Android项目组件化架构 - Go语言中文社区

Android项目组件化架构


前言

用android studio开发的同学应该都知道,androidstudio的架构是project-module形式,也就可以理解为一个项目由多个模块组成。在刚接触android studio时,它的这种架构引出了我一个想法------我们的app也可以使用这样的架构,一个app由多个模块组成,各个模块在自己的module包里。

例如我有一个资讯类app,有几大模块:首页、视频、我,那么我们构建项目时,就可以分开三个module来构建这几大模块,而不是把这几大模块都放在一个module里面。这个想法我很早就有,也付出实践,但是初期的android studio效率非常差,以此想法来构建项目,在编译时非常耗时,几分钟,十几分钟,半个小时,甚至缓不过来,一直在那转圈,于是我就此作罢。

  再到后来,有个新名词出现了,叫“组件化架构”,翻阅了一些相关文章,正是我之前研究的东西,不过加入了一些更好更完善的思想。这里我结合大牛们的思想和我自己的一些思想,来谈一下项目的“组件化架构”。

 

初始模型

我以一个实际例子来一边讲解一边搭建项目结构,就以我们上面举的资讯类app例子,它包含三个业务模块:首页、视频、我。

  那么我们的项目初始架构会是这样的:

  相信看完这个图,你脑海里会马上浮现两个疑问:app外壳是什么?为什么又多了个main组件,这个家伙用来干什么的?

这里我们可以做一个比喻,把我们的app比喻成一个电脑主机,那么app外壳就是主机外壳,main组件就是主板,其他各个组件就类似于网卡、显卡之类的东西,各个组件连接到主板上,安装在主机壳中,对外展示为一个单一的电脑主机。在我们的项目中,实际展示出来的效果应该是这样的:

        

        或者我们可以用另外一种目录结构来看,可能更清晰

        

        

这里需要注意一下,main组件的gradle文件中,apply plugin使用的是com.android.application

 

其他业务模块,使用的是com.android.library


因为在最终的发布版中,其他业务组件是集成到main组件中的,main组件编译生成最终的application,或者可以这么理解,main组件和app外壳是我们app的必备组成部分,一起构成了可对外发布的完整app,其他组件可以以集成进来,也可以不集成进来,只会增加或者减少我们app的功能,但不影响我们app的最终发布。所以我们在新增module的时候,选择的类型应该是library。


各个组件都建立完成之后,接下来可以把组件集成到main组件中,集成非常简单,只需在main组件的gradle文件中,添加如下语句

dependencies{
     ……
    compile project(':home')
    compile project(':personal')
    compile project(':video')
}

接下来做些有实际效果的事情,给各个组件写一个页面,再集成到我们的main组件中展示。

 

 

实现非常简单,就是在三大业务组件中,分别定义一个fragment,然后在main组件中,把这些fragment实例化加载到主页面就行。由于我们前面已经把三大业务组件都引入到了main组件中,所以编码和在同一个module中的编码是一样的,不需要做特别处理。

  到此就实现了“组件化架构”了么?当然没那么简单,目前只是非常简单的集成。“组件化”不仅仅是把各个功能模块分开,还有模块之间如何通信,以及“组件化”的一大亮点------各个组件可以单独开发。


 各组件单独开发

  前面我们把各个组件集成到到main组件,现在我们把组件拆出来,单独开发,开发完后,可以再把组件集成到main中,发布。这是组件化开发的最大亮点(优点)。

  这里我们把home组件单独出来开发。第一步,需要把其library模式改为application模式,因为只有application才可以单独运行,library必须依靠application才能运行。所以,接下来,我们就要在home所对应的gradle文件中做修改。

 

修改成

等要集成到main组件时,又得改回来,如果这样子手工去改,组件一多,修改起来麻烦,也不优雅。优雅的解决办法就是,设置一个开关,打开时,就是application模式,可以单独开发,关闭时,就是library模式,可以集成到main组件中。

  在项目根目录下,有一个gradle.properties文件

在这文件中,我们可以添加一个常量isDebug,值设为true。

 

这里设置了常量之后,我们项目中的其他build.gradle文件都可以把这个常量读取出来,所以我们可以在home的build.gradle文件中,读取该常量的值,动态设置applyplugin

这样子设置之后,当我们需要切换模式时,只需要修改gradle.properties文件中isDebug的值,修改完成之后,点击Project sync按钮同步一下,如果有报错,那么还有个地方需要修改一下,就是main组件的build.gradle文件,看下图

 

为什么做这样的修改?因为我们把module的模式改成了application了,这里不能引入application,引入的话会报错,所以当是debug模式时(也就是组件单独开发时),这里就不引入该组件,免得报错。

  接下来,还得修改 AndroidManifest.xml。因为当我们把一个module设置为application时,其AndroidManifest.xml需要包含一个app所需要的属性,例如appiconthemelaunch Activity这些属性设置,而当modulelibrary时,上面所提的这些属性都不需要用到,所以当我们处于不同模式时,AndroidManifest.xml文件也得不同。我们在homesrc文件夹中新创建一个目录为debug目录,再把我们用于debug时的AndroidManifest.xml文件放进去。由于在Android目录模式下比较难创建这个目录,所以我们可以选另外一种目录模式(Project目录模式),然后再创建debug目录(很绕,直接看下图就会明白了)。

 

AndroidManifest.xml文件内容可以这样写

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zhuang.home">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
    </application>

</manifest>
以上内容会有很多错误提示,其实提示的无非就是资源找不到,我们只需要把这些资源给加上就好了。可以把main组件中相应的资源拷贝过来,放在对应的目录下就可以了。

  接下来在home组件的build.gradle文件中,指定不同模式下的AndroidManifest.xml文件。

sourceSets {
    main {
        if (isDebug.toBoolean()) {
            manifest.srcFile 'src/main/AndroidManifest.xml'
        }
    }
}

以上设置完成,并且sync project之后,home组件会是这样的目录

  并且android studio的运行app里面,多了一个可运行的app,就是home。

 

 

由于我们home组件集成到main组件中时,是以一个fragment界面集成上去的,所以我们的home组件中,没有一个MainActivity可以作为LaunchActivity,我们可以创建一个,然后把fragment在MainActivity的layout  xml文件中放进去就可以了。

 

  所对应的activity_main.xml布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:name="com.zhuang.home.HomeMainFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</fragment>

以上步骤完成之后,home就可以单独作为一个app来开发了,我们可以直接运行该组件。

 

效果图

 

  再来尝试一下把home组件集成到main组件中,我们只需把isDebug的值修改为false,然后syncproject,此时可以看到我们的home作为app时被打了个叉叉,表示不能用了。

选择一下main组件,运行。

运行成功,又集成到我们的完整项目里面了。

  至此,我们已经可以随意的集成或者拆分组件,这里总结一下“组件化”的优点:

1、  业务模块分开,解耦的同时也降低了项目的复杂度。

2、  开发调试时不需要对整个项目进行编译。

3、  多人合作时可以只关注自己的业务模块,把某一业务当成单一项目来开发。

4、  可以灵活的对业务模块进行组装和拆分。

  总而言之,言而总之,就是把一个项目分开成多个项目所具有的优点。


 共用资源的引用

实际项目中,总有一些资源需要共用,例如第三方库、自定义的工具类、自定义的view等等,甚至我们的theme。那么这些资源在“组件化”中,应该如何处理?每个组件都各自引入?

  如果每个组件都各自引入这些共用资源,一方面效率差,如果一个类是所有组件都用到的,那么就得每个组件中都新增这个类,一旦这个类修改了,还得一个组件一个组件的来修改这个类;另一方面,第三方类库也有可能会冲突,例如不同组件引用了相同的第三方库,但是版本不一样,就冲突了。还有诸多问题,就不一一列举,所以,我们得用另外的方式来处理。

  解决的办法就是,也把共用库组件化,然后其他组件引用它,所以我们的架构可以演化为这样:

 

在项目里面把这个共用库加上

 

  然后可以把其他第三方库、自定义view、工具类、公用资源放进该组件。例如网络请求框架retrofit,在common的build.gradle文件中:


然后其他组件,都分别引用common组件,在其他组件对应的build.gradle文件中

这样就可以使用common组件的资源了。

看到这里,不知道大家发现没,其实我们还可以更简化,每一个组件的build.gradle所引入的资源,其实有很多是重复的,我们一样可以把这些一并加到common组件中去,例如下面这一段,就是所有组件都有的


所以这一部分,也可以放到common里面去。所以三大业务组件里面的build.gradle文件就变成这样:


除了第三方库,还有自定义的工具类、自定义的view等等这些,就直接在common中编写java文件就行啊,其他组件能够引入这些类的。

  还有就是图片、xml这些(value目录下的各种xml文件),也是可以一样被其他组件引用,这里需要特别注意的是style.xml文件,对于全局共用的style,我们应该把它也放在common中。例如我们的项目theme,本来是放在main组件的style里面,我们可以把它移到common中,这样其他组件调试时,作为一个单独的项目,也能和主项目有一样的主题。

  总之就是,所有你认为可以被各个组件共享的资源,都可以放在common组件中。

 

资源冲突

多组件集成时,其资源文件会被归档到一起,所以如果命名重复,那么就会发生冲突,导致界面混乱。为了解决这个问题,我们可以让各个组件中的资源都有一个属于自己的前缀,例如home组件中的资源,我们可以以home_开通,video组件中的资源,我们可以以video_开头,这样就防止了资源冲突。在这里gradle可以帮我们做一点事情,就是让我们在命名资源文件时,帮我们先加上前缀。例如在home组件的build.gradle文件中,加入

resourcePrefix"home_"
这样之后我们的xml文件如果没有以home_为前缀的话,就会报错。但是这个功能其实很弱,例如xml文件报错,但是我们运行的时候,依然可以运行,图片文件不已home_为前缀,也不会报错,所以,资源冲突的问题,还需要开发者自己多多注意。

 

组件之间的通信

  组件虽然可以拆分了,但是当他们集成到main组件中,一起工作时,还是需要一定的通信,例如业务A需要调用到业务B的一个页面,甚至进行传参。但是在“组件化”模式下,业务A和业务B是完全分开的,在业务A的认知里,根本就不存在业务B,也无从直接调用。当然,如果要A与B可以直接通信,也是可以的,互相引入就可以,但是这样的话,项目耦合性太高,架构也混乱,会把“组件化”的所有优点都一一撇掉。我们应该用另外一个方式来处理。

  这里需要引入一个概念----“路由”,就如我们实际访问网络一样,我们电脑发送的请求都经过路由器转发,在“组件化”中,我们也可以设置这么一个中转站,来统一处理不同组件之间的调用关系。支持我们这一思想有一些比较有名的开源项目,如ARouter、ActivityRouter,通过这些开源项目,我们可以用一个url,由其转发,帮我们调用其他组件。具体使用方法大家自行去看,这里我并不打算用这两个项目,因为我觉得我们暂且没需要用到这么强大的工具,对其他组件的调用,我们可以简单的这么写

try {
    Class clazz = Class.forName("com.zhuang.personal.MainActivity");
    Intent intent = new Intent(context,clazz);
    startActivity(intent);
} catch (ClassNotFoundException e) {
    Log.e("zhuang","未集成,无法跳转");
}

通过完整的类名来进行跳转。在debug模式下,调用其他组件时,找不到对应组件,不会直接报错,只是提示“未集成,无法跳转”。我们可以把这个方法写成一个工具类,放在common组件中。

 

publicclass EventUtil{

    /**
     * 页面跳转
     * @param context
     * @param className
     */
    public static void open(Context context,String className){
        try {
            Class clazz = Class.forName(className);
            Intent intent = new Intent(context,clazz);
           context.startActivity(intent);
        } catch (ClassNotFoundException e) {
            Log.e("zhuang","未集成,无法跳转");
        }
    }

    /**
     * 页面跳转,可以传参,参数放在intent中,所以需要传入一个intent
     * @param context
     * @param className
     * @param intent
     */
    public static void open(Context context,String className,Intentintent){
        try {
            Class clazz = Class.forName(className);
           intent.setClass(context,clazz);
           context.startActivity(intent);
        } catch (ClassNotFoundException e) {
            Log.e("zhuang","未集成,无法跳转");
        }
    }
}

这样跳转的方法就可以写成

EventUtil.open(context,"com.zhuang.personal.ShareActivity");

还有一种通信情况是组件A的改变会影响到组件B的改变。以我们这个例子来讲,personal组件中,用户的登陆情况,会影响到home组件中一些控件的显示情况,这个时候我们可以用android的广播机制,或者用EventBus来解决。不过,实际上,如果是像这种会影响全局的改变,应该放在common组件之中。例如用户,是影响全局的存在,那么就可以在common中,定义一个用户单例,其他组件分别拿这个单例去控制其界面的显示,特别是引入databingding之后,操作起来更为方便。所以,概念我们放在这里,实际操作可以根据实际情况来做不同的实现。

 

统一版本号

各个组件的build.gradle文件中,有很多版本号。

我们可以把这些版本号统一管理起来,免得每次修改都得同时修改多份build.gradle文件,也避免不同的组件使用的版本不一样,导致冲突。

在项目build.gradle文件中(项目根目录下的build.gradle文件),定义版本号常量

ext {
    compileSdkVersion = 25
    buildToolsVersion = "25.0.2"

    minSdkVersion = 14
    targetSdkVersion = 25
    versionCode = 1
    versionName = "1.0"
}



 

然后在各个组件的build.gradle文件中,做这样的修改

相信大家很容易看得明白,就是把版本号用我们定义的常量来表示。

 

 未解决的问题

  “组件化”之后,每一个业务组件中,都会有两份AndroidManifetst.xml文件,当我们用android studio自带的功能创建Activity或者Service等android组件时,会自动的在AndroidManifetst.xml文件中为我们注册。但是我们这里有两份AndroidManifetst.xml文件,android studio只会自动的在main目录下的AndroidManifetst.xml文件自动注册,所以如果是业务组件单独开发时,就得手动把那些注册的android组件复制到debug目录下的AndroidManifetst.xml文件。不知道是否有什么方法可以另其两份AndroidManifetst.xml文件都能够自动注册?

 

后话

         由于本项目并没有太多业务逻辑,实际情况中要比这更复杂,如果大家有什么问题,欢迎留言讨论,一起解决。

  项目地址 : https://github.com/likeadog/ComponentApplication/


版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weihuangcool/article/details/78109422
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-31 12:51:08
  • 阅读 ( 1154 )
  • 分类:架构

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢