首先我们需要理解,组件化是一种对完整Android项目模块进行分层的一种架构方式,如果了解过模块化的同学会发现,组件化和模块化的本质上并没有什么不同。废话不多说,接下来我们直接带着大家一起进行基本的组建化搭建,之后我们还会对架构进行整体优化
组件化的基本实现
第一步,进行项目分层
通常我们可以将组建化架构的Android项目分为三或四层,如下图
//1、分层划分:3-4层
//- app(application) 项目入口
//- business(application) 业务层,项目的一些基本业务都在这
//- core(application) 核心层,包含一些公用功能组件,例如音视频播放器等
//- library(library) 依赖库层,包含一些项目中用到的依赖库
然后,我们把项目创建为上述想要的架构方式,创建出所需要的application和library层级
创建application module
创建library module
创建完成后的项目架构
第二步,属性拓展和初始化
由于模块化可以对不同的application module进行单独调试,所以我们每个模块的灵活配置就尤为重要,因此我们需要对项目添加一些扩展属性
如上图,我们添加了common.gradle文件,添加了isModuleDebug用来判断当前模块是否需要单独调试,如果isModuleDebug为true就表示该模块可以单独运行,然后我们可以在所需要判断的模块build.gradle中使用apply from的方式将common.gradle使用进行引用,然后我们在对当前模块的isModuleDebug进行判断
之后,我们也可以对ApplicationId进行相应的判断了 (只有单独调试模式时,module才需要applicationId)
与此同时,我们也需要对项目的一些初始化进行准备,对app主入口的Application进行编写和配置,我们可以在这里做一些项目的初始化配置,如下两张图
编写MyApplication继承Application
在模块中配置MyApplication
至此,我们的项目组件化的基本架构就算已经完成,接下来我们仍然需要对架构进行进一步的优化
组件化架构优化
第一步,清单文件的统一化管理
由于我们在组件化开发中,application是需要随时变更为library的,因此,我们也需要对清单文件(AndroidManifest)文件进行统一管理
首先,我们需要另外创建一个AndroidManifest.xml作为调试时使用,通常直接把原有的清单文件复制过来即可
然后,我们可以把原来的清单文件去除掉所有视图相关的配置,这样这个清单文件就可以作为该模块为library时编译使用,如下图
我们还需要把相应模块的这两个清单文件通过build.gradle文件中android内部使用sourceSets{
main{}}语法进行配置,同时判断是否为调试模式,如下图
最后,我们别忘了我们还需要在app模块下进行引用,在app下的build.gradle也通过apply将common.gradle引用进来
判断是否调试,如果是直接进行依赖即可
第二步,版本信息和依赖的统一管理
以上我们虽然实现了组建化的架构,但是版本设置不够统一化,会导致产出多余的配置信息。因此,我们需要对每个模块的版本信息和依赖进行统一化的管理配置
首先我们可以把模块所需要的版本信息转移到common.gradle中
相应的一些配置信息也转移到common.gradle中(图中为转移前)
最后把相应的依赖也转移到common.gradle中
至此,所有的模块都只留下,其余部分可以注释或者删除(根据自身需求)
apply from: "${rootProject.rootDir}/common.gradle"
我们所有的module就只剩下了common.gradle的引用,接下来我们会把所有的上述信息都在common.gradle中进行添加
正式开始迁移,接下来都是基于common.gradle扩展project.ext中添加的信息,我们先将app模块的版本信息迁移过来,如下图,等同于创建了一些常量
之后,创建一个名叫libs的map集合,将所有依赖项进行统一维护
接下来是重头戏,我们将针对application类型的module设置创建一个名叫setAppDefaultConfig的闭包(可以理解为方法的一种,可以传参)将所需要的插件、依赖库、版本信息、SourceSets(找到对应的清单文件)等信息配置到项目中
//专门来设置application module
setAppDefaultConfig = {
project ->
project.apply plugin: 'com.android.application'
project.apply plugin: 'org.jetbrains.kotlin.android'
setAndroidConfig project.android
setDependencies project.dependencies
}
setAndroidConfig = {
android ->
android.compileSdk = project.compileSdk
android.defaultConfig {
minSdk project.minSdk
targetSdk project.targetSdk
versionCode project.versionCode
versionName project.versionName
//如果是非主app并且在调试模式下的模块,给applicationId添加一个后缀,防止applicationId冲突的问题
if (project.name == 'app') {
applicationId project.applicationId
} else if (project.isModuleDebug) {
applicationId project.applicationId
applicationIdSuffix = project.name
//举个例子,添加后这句后,若main模块单独编译,则会在applicationId后面添加main
}
//ARooter的额外配置
javaCompileOptions {
annotationProcessorOptions {
//添加路由每个模块的名称,最好用+=来添加参数,防止参数和其他框架同名的问题
arguments += [AROUTER_MODULE_NAME: project.getName()]
}
}
}
android.buildTypes {
release {
minifyEnabled false
proguardFiles android.getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
android.compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
android.kotlinOptions {
jvmTarget = '1.8'
}
//同样把sourceSets也转移到这里
android.sourceSets {
main {
if (project.name != 'app' && project.isModuleDebug) {
//以后创建application也需要创建debug下的AndroidManifest文件(需要制定开发的规范性)
manifest.srcFile "src/debug/AndroidManifest.xml"
} else {
manifest.srcFile "src/main/AndroidManifest.xml"
}
}
}
}
同时,我们也将针对library类型的module设置创建一个名叫setLibsDefaultConfig的闭包
//专门来设置library module
setLibsDefaultConfig = {
project ->
project.apply plugin: 'com.android.library'
project.apply plugin: 'org.jetbrains.kotlin.android'
setAndroidConfig project.android
setDependencies project.dependencies
}
我们会发现,上述的两个闭包同时还调用了 setDependencies这个闭包,它将提供项目的共有依赖项,我们也把它创建出来,如下代码
//设置依赖
setDependencies = {
dependencies ->
// implementation project(':business:main')
// implementation project(':business:login')
//这里将delegate代理设置为dependencies这样如下依赖就可以在dependencies中寻找了,否则会找不到
delegate = dependencies
/**
* 这里会遍历已经声明的map集合,然后分别进行依赖。
* 注意:这里会出现多个module同时依赖的情况,单实际单独调试时本就已经节约了百分之80的时间,而且gradle会把依赖除重,所以不用担心
*/
project.libs.each {
key, value -> implementation value
}
project.apts.each {
key, value -> annotationProcessor value
}
//可以用如下方式直接依赖,也可以通过上述声明方式更便捷的管理依赖
// implementation 'androidx.core:core-ktx:1.7.0'
// implementation 'androidx.appcompat:appcompat:1.3.0'
// implementation 'com.google.android.material:material:1.4.0'
// implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
//独立的依赖方式可以放在这里
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//只有module为app主入口且非单独调试模式时进行依赖
if (project.name == 'app' && !project.isModuleDebug) {
implementation project(':business:main')
implementation project(':business:login')
}
}
最后,我们在project.ext扩展的外部添加setAppDefaultConfig闭包和setLibsDefaultConfig闭包的调用
if (project.name == 'app' || isModuleDebug) {
//除了app,还有组建单独调试的情况,此类情况直接调用setAppDefaultConfig
project.setAppDefaultConfig project
} else {
project.setLibsDefaultConfig project
}
至此,我们的gradle整体迁移就算完成了 ,common.gradle完整代码如下
//添加项目的属性扩展,这里主要是作为公用的gradle处理
project.ext {
//当前Module是否单独调试 false表示不单独调试
isModuleDebug = true
applicationId = "com.componential.demo"
minSdk = 21
targetSdk = 32
versionCode = 1
versionName = "1.0"
compileSdk = 32
//对所有依赖项进行统一管理 key简写即可 value是实际依赖项的全名
libs = [
"ktx" : "androidx.core:core-ktx:1.7.0",
"appcompat" : "androidx.appcompat:appcompat:1.3.0",
"material" : "com.google.android.material:material:1.4.0",
"constraintlayout": "androidx.constraintlayout:constraintlayout:2.0.4",
]
//把ARooter也依赖进来
apts = [
"ARouterCompiler": "com.alibaba:arouter-compiler:1.5.2",
]
//专门来设置application module
setAppDefaultConfig = {
project ->
project.apply plugin: 'com.android.application'
project.apply plugin: 'org.jetbrains.kotlin.android'
setAndroidConfig project.android
setDependencies project.dependencies
}
setAndroidConfig = {
android ->
android.compileSdk = project.compileSdk
android.defaultConfig {
minSdk project.minSdk
targetSdk project.targetSdk
versionCode project.versionCode
versionName project.versionName
//如果是非主app并且在调试模式下的模块,给applicationId添加一个后缀,防止applicationId冲突的问题
if (project.name == 'app') {
applicationId project.applicationId
} else if (project.isModuleDebug) {
applicationId project.applicationId
applicationIdSuffix = project.name
//举个例子,添加后这句后,若main模块单独编译,则会在applicationId后面添加main
}
//ARooter的额外配置
javaCompileOptions {
annotationProcessorOptions {
//添加路由每个模块的名称,最好用+=来添加参数,防止参数和其他框架同名的问题
arguments += [AROUTER_MODULE_NAME: project.getName()]
}
}
}
android.buildTypes {
release {
minifyEnabled false
proguardFiles android.getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
android.compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
android.kotlinOptions {
jvmTarget = '1.8'
}
//同样把sourceSets也转移到这里
android.sourceSets {
main {
if (project.name != 'app' && project.isModuleDebug) {
//以后创建application也需要创建debug下的AndroidManifest文件(需要制定开发的规范性)
manifest.srcFile "src/debug/AndroidManifest.xml"
} else {
manifest.srcFile "src/main/AndroidManifest.xml"
}
}
}
}
//专门来设置library module
setLibsDefaultConfig = {
project ->
project.apply plugin: 'com.android.library'
project.apply plugin: 'org.jetbrains.kotlin.android'
setAndroidConfig project.android
setDependencies project.dependencies
}
//设置依赖
setDependencies = {
dependencies ->
// implementation project(':business:main')
// implementation project(':business:login')
//这里将delegate代理设置为dependencies这样如下依赖就可以在dependencies中寻找了,否则会找不到
delegate = dependencies
/**
* 这里会遍历已经声明的map集合,然后分别进行依赖。
* 注意:这里会出现多个module同时依赖的情况,单实际单独调试时本就已经节约了百分之80的时间,而且gradle会把依赖除重,所以不用担心
*/
project.libs.each {
key, value -> implementation value
}
project.apts.each {
key, value -> annotationProcessor value
}
//可以用如下方式直接依赖,也可以通过上述声明方式更便捷的管理依赖
// implementation 'androidx.core:core-ktx:1.7.0'
// implementation 'androidx.appcompat:appcompat:1.3.0'
// implementation 'com.google.android.material:material:1.4.0'
// implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
//独立的依赖方式可以放在这里
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//只有module为app主入口且非单独调试模式时进行依赖
if (project.name == 'app' && !project.isModuleDebug) {
implementation project(':business:main')
implementation project(':business:login')
}
}
}
if (project.name == 'app' || isModuleDebug) {
//除了app,还有组建单独调试的情况,此类情况直接调用setAppDefaultConfig
project.setAppDefaultConfig project
} else {
project.setLibsDefaultConfig project
}
第三步,实现组建间通信
最后的最后,由于组建间本身是没有通信手段的,我们还需要依赖一些手段来实现组建化通信。目前市面上有很多框架支持组建间通信,这里我们使用较为主流的阿里开发的ARooter框架实现组建间的通信,ARooter是一个阿里推出的路由引擎,是一个路由框架,并不是完整的组件化方案,可作为组件化架构的通信引擎,接下来我们直接在项目中依赖ARooter
//把ARooter也依赖进来
apts = [
"ARouterCompiler": "com.alibaba:arouter-compiler:1.5.2",
]
如上代码,我们先创建名叫apts的map的集合把ARooter的依赖存放进来,咱们使用当前最新的版本1.5.2,然后还需要通过each的方式遍历map集合,通过如下方式将arouter-api也依赖进来
最后,我们还需要添加一个ARouter的必添参数,直接配置在setAndroidConfig这个闭包中即可
至此,我们便完成了ARouter配置,并且完成了组建化从通信到架构的完整搭建,可以正式进入开发阶段啦,之后还可以继续对项目进行优化调整。
同时我们仍需注意的是,由于我们在上面在SourceSets中给application module设置了debug目录下的清单文件,因此,在今后的开发过程中需要针对开发人员制定相应规则,在每次创建新的module时需要在对应debug目录下创建清单文件,并把原有的清单文件改造成library所需的结构
额外拓展
本篇内容我们虽然已经做了完整的依赖和配置整合,但偶尔我们也会需要一个单独模块的定制化,因此我们仍然可以在任意模块的build.gradle中单独做配置,这样也进一步提升了项目的灵活性