前言
在Android开发中,随着项目的不断扩展,项目会变得越来越庞大,而随之带来的便是项目维护成本与开发成本的增加!每次调试时,不得不运行整个项目;每当有新成员加入团队时,需要更多的时间去了解庞大的项目。。。而为了解决这些问题,团队通常会将项目模块化,以此来降低项目的复杂度和耦合度,让团队可以并行开发与测试,让团队成员更加专注于自己所负责的功能模块开发。。。
对于一些大厂如BAT或者美团等这些大型互联网公司,都会自己造轮子,实现项目模块化。而对于中小型公司,限于成本因素,一般都是选用这些大厂造的优秀的轮子来进行项目模块化。本文以及后面一系列文章,将向大家分享我在项目模块化中的实践经验。我的项目模块化方案主要借鉴于饿了么
、微信
、美团
的模块化技术文章,如有建议,欢迎提出!
模块化需要做什么
首先,在开始项目模块化之前,我们必须要明确模块化需要做些什么?这就等于写书之前必须得有个总纲,否则越写到后面,越是混乱。以下是我认为在模块化时需要注意的几个问题:
- 如何拆分项目
- 模块之间的通信
- 模块内的代码隔离
- 模块在调试与发布模式之间的切换
在明确了项目模块化中需要解决的问题后,我们需要选定一个优秀的组件化开源框架。在本方案中,我选择阿里的ARouter
,也是目前比较流行的组件化框架之一,大家也可以选择其他开源框架。ARouter
的具体使用本文就不在介绍了,大家可以在网上自行搜索,下面开始设计项目模块化的架构。
一、如何拆分项目
在这里插入图片描述三、模块内的代码隔离
虽然在模块化时,我们已经将业务拆分成了一个一个单独的module,但是有的业务实在是庞大,即使单独拆分成一个module,仍然有些臃肿。但是如果将业务module继续拆分成单独的子module,又会显得过重。此时我们就要开始寻求比module更小一级的粒度,这一点《微信Android模块化架构重构实践》中给我们提出了Pins工程
的思路。
如上图,我们可以在module中构建粒度更小的pins工程来拆分业务,可以更加清晰直观的明确业务内的职责分工,示例代码如下:
sourceSets {
def dirs = ['p_imageview','p_progressbar']
main {
dirs.each { dir ->
java.srcDir("src/${dir}/main/java")
res.srcDir("src/${dir}/main/res")
}
}
}
上述代码做到的是仅仅是分割代码,pins工程之间还可以相互调用。而微信提出的是工程之间没有依赖关系存在,则不可以相互调用,类似于两个module之间,如果没有在gradle中注明依赖,则不可相互调用。所以,有些人就会提出,既然如此,为何不直接拆将Pins工程
完全拆分成module?
论坛中开发者们给出的最多的答案是提升编译速度!不过快手的一位资深Android工程师说,微信内部已经放弃了Pins工程
,虽然不知道是真是假,不过美团在借鉴微信的Pins工程
时,也没有提出代码的完全隔离。就个人而言,我认为Pins工程
是module化的一个过渡阶段,当Pins工程
的体量达到一定程度时,必须进行完全的module化,所以也没有必要做到Pins工程
之间的代码完全隔离,做到以上即可。如果有想实现代码完全隔离的,可以参考《Android Pins 工程结构》
四、模块在调试与发布模式之间的切换
项目开发时,一般提供调试环境与正式发布环境。在不同的环境中,app的有些功能是不需要用到的,或者是有所不同的。另外,在模块化开发时,有些业务模块在调试时,可以作为单独的app运行调试,不必每次都编译所有的模块,极大的加快编译速度,节省时间成本。基于,以上种种原因,我们就必须对项目的调试与正式环境做不同的部署配置,然而如果全靠每次手动修改,当模块量达到数十时,则会非常麻烦,且容易出错。所以我们需要尽可能的用代码做好配置。
首先,来看如何配置module在app与library之间的切换,实现module在调试时作为app单独运行调试。看示例代码,这也是比较常见的方式:
- 在
gradle.properties
中,配置字段isAuthModule
,控制auth
模块是作为一个模块还是一个app
# true: 作为一个模块
# false: 作为一个app
isAuthModule=true
- 在
auth
模块的build.gradle
文件中作如下配置:
// 获取 gradle.properties 文件中的配置字段值
def isApp = !isAuthModule.toBoolean()
// 判断是否为 app,选择加载不同的插件
if (isApp) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
......
android {
defaultConfig {
// library 是没有 applicationId 的,只有为 app 时,才有
if (isApp) {
applicationId "com.homeprint.module.auth"
}
......
}
sourceSets {
main {
// 作为app与模块时的AndroidManifest.xml会有所不同,在不同状态时选择不同的AndroidManifest.xml
if (isApp) {
manifest.srcFile 'src/main/AndroidManifest.xml'
} else {
// 记得在 main 文件夹下创建 module 文件夹,添加AndroidManifest.xml
manifest.srcFile 'src/main/module/AndroidManifest.xml'
}
}
}
这样只要我们更改一下,gradle.properties
文件中的配置字段,就可以自由实现module在模块与app之间的切换。下面我们再来看下,如何实现app在调试与发布环境时,加载不同的模块,看以下示例:
假如有两个模块,lib_debug 与 lib_release,lib_debug 是只有在调试环境才需要使用,
lib_release 是只有在正式环境才需要使用,以下提供两种方式实现
方式一:
if(mode_debug){
implementation project(':lib_debug')
}else{
implementation project(':lib_release')
}
方式二:
debugImplementation project(':lib_debug')
releaseImplementation project(':lib_release')
以上两种方式,方式一类似于上述的模块在 app 与 library 之间的切换,方式二是使用 gradle 提供的方法实现
推荐文章
《Android:项目模块化/组件化的架构之路(二)》
《Android:超详细的本地搭建maven私服以及使用Nexus3.x搭建maven私服的讲解》
《美团外卖Android平台化架构演进实践》
《微信Android模块化架构重构实践》
《Android工程模块化平台的设计》
《Android工程模块化平台的设计(整理优化)》
《Android Pins 工程结构》
《pins工程及自动生成文件夹》
网友评论