Android 组件化架构思路

1,330 阅读6分钟

为什么要模块化/组件化

随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发。

image.png 上图是目前比较普遍使用的Android APP技术架构,往往是在一个界面中存在大量的业务逻辑, 整个项目中也没有模块的概念,只有简单的以业务逻辑划分的文件夹,并且业务之间也是直接相互调用、高度耦合在一起的;

模块化/组件化的划分

通过对项目业务跟功能,可以先对项目中的业务和功能进行划分,拆成独立的模块跟组件。如下图,简单对我们项目进行划分拆分业务跟组建,通过组件化的方式依赖到宿主app中,最终对立成一个最终的app项目。 image.png

  • app壳工程:负责管理各个业务组件,和打包apk,没有具体的业务功能;
  • 业务组件:根据公司具体业务而独立形成一个的工程;
  • 基础组件:提供开发APP的基础功能,比如说网络框架等
  • 功能组件:集成的一些第三方sdk,独立成library,提供给各业务组件使用
  • 集成模式: 所有的业务组件被“app壳工程”依赖,组成一个完整的APP
  • 组件模式: 可以独立开发业务组件,每一个业务组件就是一个APP;

如何组件化实施流程

(1)组件模式和集成模式的转换 我们知道android studio中的module有两种属性,分别为

application属性

apply plugin: ‘com.android.application’

library属性

apply plugin: ‘com.android.library’

当我们在组件模式开发时,业务组件应处于application属性,而当我们转换到集成模式开发时,业务组件应该处于 library 属性;

如何转换?

在项目的gradle.properties文件中配置,isModule属性

# 是否为插件模式,true则各组件可独立运行,打包时需改成false
isModule=false

在业务组件build.gradle文件中通过以下配置决定当前组件是组件模式还是集成模式。

if (isModule.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

组件化中会遇到的问题

组件之间AndroidManifest合并问题

在 AndroidStudio 中每一个组件都会有对应的 AndroidManifest.xml,用于声明需要的权限、Application、Activity、Service、Broadcast等,当项目处于组件模式时,业务组件的 AndroidManifest.xml 应该具有一个 Android APP 所具有的的所有属性,尤其是声明 Application 和要 launch的Activity,但是当项目处于集成模式的时候,每一个业务组件的 AndroidManifest.xml 都要合并到“app壳工程”中,要是每一个业务组件都有自己的 Application 和 launch的Activity,那么合并的时候肯定会冲突,试想一个APP怎么可能会有多个 Application 和 launch 的Activity呢?

我们需要在build.gradle 中指定下 AndroidManifest.xml 的位置,AndroidStudio 才能读取到 AndroidManifest.xml,这样解决办法也就有了,我们可以为组件开发模式下的业务组件再创建一个 AndroidManifest.xml,然后根据isModule指定AndroidManifest.xml的文件路径,让业务组件在集成模式和组件模式下使用不同的AndroidManifest.xml,这样表单冲突的问题就可以规避了。

image.png

上图是组件化项目中一个标准的业务组件目录结构,首先我们在main文件夹下创建一个module文件夹用于存放组件开发模式下业务组件的 AndroidManifest.xml,而 AndroidStudio 生成的 AndroidManifest.xml 则依然保留,并用于集成开发模式下业务组件的表单;然后我们需要在业务组件的 build.gradle 中指定表单的路径,代码如下:

//设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
//但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
resourcePrefix "user_"
sourceSets {
    main {
        jniLibs.srcDirs = ['libs']
        if (isModule.toBoolean()) {
            manifest.srcFile 'src/main/module/AndroidManifest.xml'
        } else {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java {
                exclude 'debug/**'
            }
        }
    }
}

组件之间通信问题

在组件化开发的时候,组件之间是没有依赖关系,我们不能在使用显示调用来跳转页面了,这里我们引入阿里的路由框架解决组件之间的通信问题,这里采用的原理是使用apt技术根据注解生成对应的源码,完成跳转。 以下是Arouter的官方地址

github.com/alibaba/ARo…

library依赖问题

1:重复依赖 如果A组件跟B组件都依赖了C组件,D组建同时依赖了A,B组件,这时候就会导致重复依赖C组件的问题,此时可以通过exlude属性去除重复依赖问题

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
        exclude module: 'support-v4'//根据组件名排除
        exclude group: 'android.support.v4'//根据包名排除
    }
}

2:透传依赖 如果A组建依赖了B组建,B组件依赖了C组件,此时我们如果想在A,B组建中都用C组件里面的功能可以通过api依赖的方式,向上透传

//fastjson
api deps.fastjson_android

资源重复命名问题

比如说我们可能在A,B组件中同时创建了相同名字的文件,这时候打包就会出问题,这时候我们需要对资源文件的命名有一定的规范,通常需要我们在gradle文件中做如下配置

//设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
//但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
resourcePrefix "user_"

image.png 如上图:通常我们会在组件的资源文件中会以组件的名字作为前缀开始命名

项目混淆问题

组件化项目的Java代码混淆方案采用在集成模式下集中在app壳工程中混淆,各个业务组件不配置混淆文件。集成开发模式下在app壳工程中build.gradle文件的release构建类型中开启混淆属性,其他buildTypes配置方案跟普通项目保持一致,Java混淆配置文件也放置在app壳工程中,各个业务组件的混淆配置规则都应该在app壳工程中的混淆配置文件中添加和修改。

之所以不采用在每个业务组件中开启混淆的方案,是因为 组件在集成模式下都被 Gradle 构建成了 release 类型的arr包,一旦业务组件的代码被混淆,而这时候代码中又出现了bug,将很难根据日志找出导致bug的原因;另外每个业务组件中都保留一份混淆配置文件非常不便于修改和管理,这也是不推荐在业务组件的 build.gradle 文件中配置 buildTypes (构建类型)的原因。

组件化/模块化项目结构图

image.png

总结

项目模块化需要对项目总体进行把控,业务的划分,功能模块的抽取,以及合适的架构方案跟技术选型,这都需要花费很多精力,任重而道远。