模块化的优势有很多,一代码分离,结构清晰;二多任务协同开发,而且自己模块单独运行也更轻量。等等。最近在调研各种实现方案,这是我的自己想出来的一种。
话不多说,直接分享我的操作步骤,然后我可能会与其他实现方式做个对比。
原有项目结构
为了实现模块化,前期我们已经根据应用功能场景,进行了module拆分,为实现模块化靠拢。项目简化结构如下图
APP
是应用的入口module,我把他做得很简单,里边可以说就一个欢迎页Activity。BaseModule
是一些跟基本封装。ModuleA、B、C
是期望单独运行的Module。模块间的跳转我用的是ARouter
,这块暂不多介绍。
我们知道,每一个Project都是由多个module组成,而settings.gradle中通过include方式引入这些module,所以一开始就在想如果能在编译时动态的引入module那不就是很简单了吗。
操作步骤
一 gradle.properties配置
在项目根目录的gradle.properties中定义一个变量,为了做到变量值的见名知意,我用的String类型的
# ↓全量编译模式
#DEVELOP_MODE="all"
#
# ↓单ModuleA模块编译模式
DEVELOP_MODE="module-a"
#
# ↓单ModuleB模块编译模式
#DEVELOP_MODE="module-b"
#
# ↓单ModuleC模块编译模式
#DEVELOP_MODE="module-c"
#
...
复制代码
二 settings.gradle区分
根据gradle.properties中配置的不同运行模式,在settings中做简单的if else 判断,include不同的module
include ':APP', 'BaseModule'
if (DEVELOP_MODE == "\"module-a\"") {
include ':ModuleA'
} else if (DEVELOP_MODE == "\"module-b\"") {
include ': ModuleB'
} else if (DEVELOP_MODE == "\"module-c\"") {
include ': ModuleC'
} else {
include ': ModuleA', ': ModuleB', ': ModuleC'
}
复制代码
在这里有个写法上需要注意的是String类型的module-a
需要加\
转义,甚至调用startsWith
这样的API都要这样写DEVELOP_MODE.startsWith("\"module")
有人说这里不需要区分,直接include所有module即可。我尝试后发现确实可以,但仍会执行多余module的gradle编译,徒增编译时间,所以最好区分。
三 APP的gradle配置
上面的module关系依赖图中我们看到,APP依赖了A、B、C这三个module。在这里,我们也是需要根据gradle.properties中配置的不同运行模式,动态引入依赖
if (DEVELOP_MODE == "\"module-a\"") {
implementation project(':ModuleA')
} else if (DEVELOP_MODE == "\"module-b\"") {
implementation project(':ModuleB')
} else if (DEVELOP_MODE == "\"module-c\"") {
implementation project(':ModuleC')
} else {
implementation project(':ModuleA')
implementation project(':ModuleB')
implementation project(':ModuleC')
}
复制代码
好了,一个简单的模块化就实现了,如果想单独运行ModuleA,就将gradle.properties
中的DEVELOP_MODE="module-a"
这一行解开,其他注释掉,sync Now完后直接运行就OK了
拓展
一个正常的商业项目,肯定不像我前面的项目图一样简单,只有1个APP、1个Base、2个Module。我项目真实情况是类似中间的模块ABC这层就有20多个,而且不止一层;Base这层module也有五六个。不使用JIMU(积木)那种Android组件化框架的原因之一就是项目不是简简单单,关系整齐的这种依赖关系。
而且,在我们应用里,要想使用ModuleB,必须带着ModuleA的结果数据。要想B模块化后能使用,这该怎么做?需要的数据可能有:数据库、SharedPreferences、File文件,网络请求及合理的参数。
我们的做法是,为这种非正式的模块化运行添加一个,专门初始化假数据的module。在APP的欢迎页中跳转时判断,如果是模块化运行,直接往这个假数据Module跳,通过这个Module初始化好所有的数据后,再往真正的目标Module跳。
而需要准备的数据中,网络请求及合理的参数的准备,也有很多实现方案。比如就用真实的请求,比如本机运行服务。对于我做这个调研,没有找后端支持,也想过自己搭后台服务器,但是一想,搭好了服务器,也是简单的返回造好的假json串数据。所以干脆,本地处理算了,这时候okhttp的拦截器就是一个很好的应用了。我直接在拦截器中,判断请求的path,返回假的json串不就行了。
当然,还有很多模块化需要用到的小技术点,那就需要见招拆招了。
与其他模块化方式对比
(1)有一种模块化是这样写的,在ModuleA的gradle文件中
if(isBuildModule.toBoolean()){
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
...
sourceSets {
main {
if (isBuildModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
//isBuildModule是在gradle.properties中声明
isBuildModule=false
复制代码
这样写的繁琐之处是
- 需要在每个Module的gradle.properties中声明这样一个区分application还是library。
- 准备两套Manifest。
- 要想单独运行某个模块还得每次去改动settings的include,和APP的gradle中的依赖。
(2)使用JIMU(积木)这种Android组件化框架
它集成好之后的使用,虽然也很简单。但是框架的接入也是需要添加很多东西,比如每个module的properties中都需要进行配置。而且框架的一个缺点就是我们开发人员不知道它怎么实现的,遇到的编译问题也更不好查。
(3)我这样写的优缺点
优点
- 条理清晰,定义的每个地方是什么作用都很清楚
- 不修改原有module内的业务逻辑,在外边准备好所需数据有点沙盒似的设计。
- 不只能单模块运行。也可能,多个模块组成一个业务线,整体运行这个业务线也很好实现。
- 切换运行模块时,只开关gradle.properties里的配置就行。
缺点
- 新配置一个模块化需要在gradle.properties、settings.gradle、APP的gradle,这3个地方都做响应的配置。需要配的模块化运行多了,if else 类的判断也会增多,代码行数也会增加,略显繁琐。但相对于切换模块的简便,这些是值得的。
最后求助各位道友,如果,您们有其他什么好的思路,欢迎你们和我一起交流