Android模块化开发配置

模块化

  1. 项目由多个模块组成
  2. 每个模块都是一个独立的Feature或组件
  3. 业务模块间解耦不相互直接依赖
  4. 业务模块与公共组件模块通过aar依赖
  5. 每个模块独立开发,独立运行调试

模块化的好处

当一个项目越来越大,越来越复杂后,代码量就会变得越来大,难以阅读难以维护,业务之间出现耦合的可能性也会越来越大,同时整个APP编译调试的时间也会越来越长。

而使用模块化开发则可以解决以上问题:

  1. 项目代码结构清晰,每个Feature和公共组件都是一个独立的Library模块
  2. 避免每个Library模块间的直接耦合
  3. 提升模块的复用性
  4. 单个模块独立编译调试速度更快,节省开发时间
  5. 只关注自己所在的模块,从而避免其他Feature的异常block自己的Feature开发

但是…这篇文章不是教你如何进行模块化开发,而是介绍如何进行模块化开发的工程配置,以满足模块化开发过程中的多团队协作问题。

模块化开发配置

我们先创建一个Android工程,这个工程除了有一个app的主module之外,还有两个library类型的module,工程结构如下图:

上图中module1和module2就代表了两个不同的业务module

模块化开发配置需要解决哪些问题呢?

module配置参数化

大家都知道一个项目的主module存在一个build.gradle文件,里面有如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 28
defaultConfig {
applicationId "xxxxxxxxx"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
....
}

而工程中的library类型的moudle也有一个build.gradle文件,它的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 28

defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
...
}

通过上面的配置内容可以发现主module和library类型的module除了plugin不一样之外,主module会比library类型module只多一个applicationId。

所以如果我们想让某个library类型的module能独立运行调试,我们可以通过参数控制,动态的为该module添加相关配置,具体脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//common-build.gradle
project.ext {
mainModuleType = "mainModule"//主module
debugLibraryModuleType = "debugLibraryModule"//可单独运行的library module
libraryModuleType = "libraryModule"

//根据module类型动态添加对应的配置
configModuleGradleScript = { moduleType ->
applyPlugin(moduleType)
applyAndroidDefaultConfig()
applyApplicationId(moduleType)
}

//配置module的编译版本相关配置
applyAndroidDefaultConfig = {
project.android.compileSdkVersion compileSdkVersion
project.android.defaultConfig.targetSdkVersion targetSdkVersion
project.android.defaultConfig.minSdkVersion minSdkVersion
}

//根据module类型动态添加对应plugin
applyPlugin = { moduleType ->
if(moduleType == libraryModuleType) {
project.apply plugin: 'com.android.library'
project.description "library"
} else {
project.apply plugin: 'com.android.application'
project.description "app"
}
project.apply plugin: 'kotlin-android'
project.apply plugin: 'kotlin-android-extensions'
}

//根据module类型动态添加对应的applicationId
applyApplicationId = { moduleType ->
if(moduleType == mainModuleType) {
project.android.defaultConfig.applicationId applicationId
} else if(moduleType == debugLibraryModuleType) {
project.android.defaultConfig.applicationId applicationId + "." + project.name
}
}
}

可以看到所有的动态配置脚本我都是通过在project.ext中添加闭包实现的,这样做的好处是在其他脚本文件中也可以引用project.ext中定义的闭包和变量

另外上面的配置中,在配置可单独调试library module时,我对其applicationId添加一个工程名称作为后缀,这样可以对主app的applicationId进行区分。

上面脚本里的注释提到了可单独运行的library module,那这是什么意思呢?
我们每个工程打包为apk时只能有唯一一个plugin为com.android.application的主module,而其他需要集成的moudle的plugin均为com.android.library,当开发某个业务module(library类型)时,我们需要该module能单独运行以方便我们调试同时节省编译时间,这时我们就需要通过gradle参数控制,将其plugin暂时变为com.android.application以便使其能独立运行,所以这个时候该module也是一个主module,但为了与apk的主module进行区分,所以我把它叫做可单独运行的library module

那具体如何通过参数控制将某个library module暂时变为可单独编译运行的module,而在集成的时候又设置为library类型的module呢?我们可以在工程根目录的gradle.properties文件中添加参数进行控制

1
2
//gradle.properties文件
debugLibraryModules=[module1]

上面的debugLibraryModules参数将module名称为module1的module设置为可单独运行的library module,这个参数是一个数组,可以配置多个module。当这个数组为空的时候就代表不设置任何library module。

通过这个参数我们就可以在每个module工程加载脚本时判断当前module是否为可单独编译运行的module,判断方法如下:

1
2
3
4
5
6
7
project.ext {
//通过module的名称进行判断
isDebugLibraryModule = { projectName ->
def debugLibraryModuleList = debugLibraryModules
return project.hasProperty('debugLibraryModules') && debugLibraryModuleList.indexOf(projectName) != -1
}
}

到这里,我们的参数化动态配置脚本的基础已经完成了,接着要针对主module和library module进行具体的工程配置。

配置app的主module

1
2
3
4
5
6
7
8
//main-module-build.gradle
configModuleGradleScript(mainModuleType)

getRootProject().getSubprojects().each {item ->
if(item.name != project.name && !isDebugLibraryModule(item.name)) {
project.dependencies.add("implementation", project(":$item.name"))
}
}

因为app的主module是不可能变的,所以该module的moduleType肯定是mainModuleType

另外app在集成其他library module时必须要将其他library module添加为主module的工程依赖,所以上述脚本中使用getRootProject().getSubprojects()先找到工程的所有module,然后将library module都动态添加为主module的工程依赖

主module的build.gradle内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apply from: "${rootProject.rootDir}/buildScript/common-build.gradle"
apply from: "${rootProject.rootDir}/buildScript/main-module-build.gradle"

android {
defaultConfig {
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

在该工程的主module动态添加工程源码依赖时,也可以通过参数来控制某个module到底是采用工程源码依赖,还是采用远程仓库的aar依赖,以此来满足不同的业务需要。这篇文章就不介绍了,相信看懂这篇文章的同学应该能自己实现这个需求。

配置library module

1
2
3
4
5
6
7
8
9
//library-module-build.gradle
def getModuleType() {
if(isDebugLibraryModule(project.name)) {
return debugLibraryModuleType
} else {
return libraryModuleType
}
}
configModuleGradleScript(getModuleType())

在配置library module时,要先判断当前library moduel是否已经通过参数设置为可单独运行的module,如果是的话则该module调用闭包configModuleGradleScript的参数为debugLibraryModuleType,否则则为libraryModuleType

library module的build.gradle内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apply from: "${rootProject.rootDir}/buildScript/common-build.gradle"
apply from: "${rootProject.rootDir}/buildScript/library-module-build.gradle"

android {
defaultConfig {
versionCode 1
versionName "1.0"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

sourceSets {
main {
if(isDebugLibraryModule(project.name)) {
manifest.srcFile 'src/debug/AndroidManifest.xml'
java.srcDirs = ['src/debug/java', 'src/main/java']
res.srcDirs = ['src/debug/res','src/main/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
resources {
//排除java/debug文件夹下的所有文件
exclude 'src/debug/*'
}
}
}
}
}

上述脚本在配置可单独运行的library module时,虽然已经动态将其plugin设置为com.android.application也添加了applicationId,但是该module依旧还不能单独运行,因为我们一开始创建的library module中的AndroidManifest.xml文件没有配置也不能配置启动Activity,所以需要在library module的buld.gradle脚本中添加如下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sourceSets {
main {
if(isDebugLibraryModule(project.name)) {
manifest.srcFile 'src/debug/AndroidManifest.xml'
java.srcDirs = ['src/debug/java', 'src/main/java']
res.srcDirs = ['src/debug/res','src/main/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
resources {
//排除java/debug文件夹下的所有文件
exclude 'src/debug/*'
}
}
}
}

上面的脚本先检查当前module是否为可单独运行的library module,如果是则采用src/debug/AndroidManifest.xml文件。要注意的是该文件必须要包含’src/main/AndroidManifest.xml’文件的所有内容,同时还要设置启动Activity

到这里,模块化开发的工程配置介绍完了,有兴趣的同学可以在这https://github.com/huyongli/AndroidModuleDesign查看完整代码


原创文章,转载请出处注明。

下面是我的个人公众号,欢迎关注交流

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×