原文链接:https://blog.lujun.co/2017/02/15/android-to-modularity/
组件化?
模块化
曾经也许你做过这样的事,将一些公共的代码组成独立的 module 并编译为 library 供各个子模块或其他项目引用,通常利用这种方式实现项目模块化(小功能独立拆分)。
组件化
那么什么是组件化?概括为:组件化是基于可重用目的将项目按照具体业务需求进行拆分,并能将拆分得到的组件进行灵活重组,减小耦合(业务需求上的拆分)。
模块化与组件化
组件化和模块化,两者都是为了实现解耦/重用而拆分项目为多个模块;相对于模块化,组件化的拆分粒度更大。
移动应用中项目组件化的目的就是让若干业务 module 能够并行开发,每一个业务 module 都能生产与之对应的 library 与 App,最终能够根据具体的需求灵活的组织不同的业务 module 生产主 App。对于 Android 项目中组件化的概念,可以看看这个 PPT。
为什么组件化?
- 公共代码/业务代码解耦
- 共享模块
- 组件 module 代码的控制/编写/修改能保证不相干组件 module 代码的稳定性(不需要修改其他组件的业务,避免产生相关干扰,还能提高测试效率)
- 减少具体业务 module 的编译时间,提高开发效率(Android 编译...)
Android 项目组件化
组件化结构
Android 中组件化主要是通过脚本来控制,接下来以 Android Studio 中 Gradle 脚本为例来实现简单的一个项目组件化。这里不会讲解如何对主体业务进行组件化的拆分,因为不同的业务在不同的需求下都能产生一套合适自身的拆分规则。
组件化首先要保证不同的业务 module 既能够单独的生产、调试,也能够灵活的作为一个业务分支组建到主 App 中。我们知道,通过控制 gradle 脚本中的apply plugin: 'com.android.xxx'
可以实现 Android module 运行于不同的状态,常用做法是自定义一个变量去控制当前业务 module 所处的状态。脚本如下:
if (isDevModel.toBoolean()) {
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
其中isDevModel
定义在主项目 gradle.properties 文件中(当然也可以定义在子业务 Modue 中)。对于基础组件,比如通用的工具类库,按照模块化思路作为一个 library 就行。
注意:在 Android 开发中当一个 module 需要有application
和library
形态时,它们分别对应的AndroidManifest.xml
文件是不一样的。所以需要为当前 module 提供两套AndroidManifest.xml
文件,再编写脚本根据当前 module 需要处于的形态自动去选择编译对应的文件,脚本如下:
android {
// ...
sourceSets {
main {
if (isDevModel.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
}
项目组件化架构图大致如下所示:
项目组件化后的结构图大致如下所示:
其中,app
module 是主 module;businessmodule
和businessmodule2
是业务 module,可以单独作为一个 Application 运行或作为主 module 的依赖组建到其中运行;baselibrary
作为基础组件 library 供其他任何 module 依赖使用。
组件化中 Module 组建
Module 之间互相依赖以便于组建满足需求的 App。举一个例子:现在需要发布 release 版本的 App,需要将 businessmodule1 业务组件发布出去,此时只需要将isDevModel
修改为false
,并在主 moduleapp
中依赖businessmodule1
module即可。依赖脚本如下:
dependencies {
// ...
if (!isDevModel.toBoolean()){
releaseCompile project(':businessmodule1')
}
}
其中,businessmodule1
module 又是依赖基础组件baselibrary
module 的:
dependencies {
// ...
compile project(':baselibrary')
}
当开发者单独对businessmodule
module 进行业务修改时,再将isDevModel
修改为true
,单独运行这一个 module 即可。
因为单个业务组件 module 的依赖相对不会太多,从而使得编译时间大大减少。
组件化遇到的问题以及一些小技巧
业务组件之间通信
不同的组件 module 之间 Activity 跳转
- 类名跳转(看这篇文章介绍),不方便携带数据
- 显示换隐式,定义 schema 跳转(外链跳转 App),可携带少量数据
Module 间通信也可采用事件总线进行
- EventBus/Otto/自定义 RxBus
不同环境业务组件使用不同环境的基础组件(解决 CI 中涉及到的自动化问题)
举个例子,比如基础组件 module 是一个网络请求库,其中涉及到了测试环境和生产环境使用不同的代码,业务组件 module 也需要根据不同的情况区别使用该网络请求库生成的 library。
基础组件 module 中根据不同的环境使用不同的代码。这里我们为baselibrary
module 的 debug 和 release 模式分别准备一套代码,整体 module 结构图如下所示:
如上图所示,debug 和 release 中都有一个包名和类名相同的Name
类,但其中的代码是不一样的。具体可以查看源码。为了能够在 debug 和 release 模式下分别使用设置的代码,需要使用sourceSets
控制代码的合并,如下:
android {
// ...
sourceSets {
debug {
java.srcDirs = ['src/main/java', 'src/debug/java']
}
release {
java.srcDirs = ['src/main/java', 'src/release/java']
}
}
}
此外在发布该 library 时,需要指定一些设置,如下:
android {
// ...
defaultConfig {
// ...
defaultPublishConfig 'release'
publishNonDefault true
}
}
说明:
- defaultPublishConfig 'release',默认 library 只会生产 release 下的版本,此版本将会被所有项目使用,通过
defaultPublishConfig
可以控制默认生产哪个版本的库。 - publishNonDefault true,默认情况下不能生产所有版本的 library,通过设置
publishNonDefault
为true
,可以同时生产所有版本的 library。
业务组件 module 依赖不同的基础组件生产的 library,如下:
dependencies {
// ...
debugCompile project(path: ':baselibrary', configuration: "debug")
releaseCompile project(path: ':baselibrary', configuration: "release")
}
在使用 CI 时,通过这样的配置脚本解决了多个 APK 包依赖同一组件生产的不同的 library,最终得到我们需要的开发/测试/生产 APK 包。
其他问题
- 资源名称冲突,可以添加
resourcePrefix
解决。注意当添加了该属性后,所有资源名称必须用此 prefix 开头,否则作为 library 时会报错。 - Application 冲突,通过设置
exclude
将业务组件 module 对应的 Application 文件隔离。具体参照 ModularityDemo。
总结
解耦、独立业务模块开发(减少编译时间),这些都应该是项目组件化的理由。同时,对一个既有项目实施组件化,考虑足够是最高效的方式。
源码
源码
参考
- http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library
- https://www.kymjs.com/code/2016/10/18/01/