Android组件化架构(三)
在讲到组件化优化之前,先从一些gradle知识点讲起。
通常一个Application Module的gradle.build如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.sun.test"
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
testImplementation 'org.mockito:mockito-core:2.18.3'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
}
第一行apply plugin: 'com.android.application'
引入了构建需要用到的Gradle插件工具库。相当于Java中的import。每一个gradle文件都相当于一个Project对象,project.apply()会加载对应的工具库。
android{}和dependencies{}是函数式方程,是闭包函数。和apply一样,可以写成project.android{}和project.dependencies{}。
相对于build.gradle 还有一个settings.gradle,里面内容如下:
include ':app','login','setting'
每当新添加一个Module时,Gradle都会自动添加相应的文件路径到include函数中。声明文件夹,并以一个模块的形式存在。
1.版本参数优化
每个Module的build.gradle都有一些必要的文件,同一个Android工程在不同的模块中会要求这些属性保持一致,例如compileSDK等等。如果引用不一致,除了可能导致潜在的问题外,还会增加APP体积,降低编译效率。如何做到统一的配置的,在每个项目中改是不现实的。
第一种方法是提供一个统一的gradle配置config.gradle。
一、在根目录创建一个gradle文件,内容如下:
ext {
compileSdkVersion : 27,
minSdkVersion : 19,
targetSdkVersion : 27,
supportVersion : '27.1.1',
}
二、在Module的首行添加该gradle文件
apply from:"${rootProject.rootDir}/config.gradle"
并在Module的gradle配置如下:
android {
compileSdkVersion ext.compileSdkVersion
defaultConfig {
minSdkVersion ext.minSdkVersion
targetSdkVersion ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
这样只要修改根目录下的config.gradle文件就能保证各个Module的版本一致。当然这个方法也可以用于一些第三方库。
第二种方法是使用Android对象配置
在config.gradle中添加如下代码:
setDefaultConfig = {
extension -> //闭包参数extension相当于android对象
extension.compileSdkVersion compileSdkVersion
extension.buildToolsVersion buildToolsVersion
extension.defaultConfig{
minSdkVersion minSdkVersion
targetSdkVersion targetSdkVersion
}
}
然后在build.gradle中就可以使用setDefaultConfig了,在每个Module的gradle中:
android {
project ext.setDefaultConfig android //调动配置
}
就能使用统一的配置。
第三种方法是使用project对象配置
这种方法和第二种很像,不过进一步的把代码抽离到config.gradle中。代码如下:
//设置APP配置
setAppDefaultConfig = {
extension->
//引用Application插件库
extension.apply plugin:`com.andorid.application`
extension.description "app"
setAndroidConfig extension.android
setDependencies extension.dependencies
}
//设置Lib配置
setLibDefaultConfig = {
extension ->
//引用lib配置
extension.apply plugin: ’com.andorid.library‘
extension.description "lib"
setAndroidConfig extension.android
setDependencies extension.dependencies
}
//设置Android配置
setAndroidConfig = {
extension ->
extension.compileSdkVersion 25
extension.buildToolsVersion "25.0.2"
extension.defaultConfig{
minSdkVersion 14
targetSdkVersion 25
...
}
}
//设置依赖
setDependencies = {
extension ->
extension.compile fileTree(dir:'libs',include:['*.jar'])
extension.annotationProcessor 'com.alibaba:arouter-compiler:1.1.1'
}
在每个build.gradle中只需添加project对象到闭包函数即可。
apply from:"${rootProject.rootDir}/config.gradle"
project.ext.setAppDefaultConfig project //如果是lib则是setLibDefaultConfig
这样就可以进一步抽离代码,改动config.gradle 即可。
2.调试优化
组件化一大利器就是可以将一个模块做成APP启动,然后单独调试。这样可以保证模块之间分离,提高开发效率。步骤如下:
1)独立模块调试需要将Library Module改成Application Module。即:
com.android.library -> com.android.application
2)每个Application都要配置ApplicationId,最好不相同,这样就可以不冲突的安装多个模块。在config.gradle中添加函数配置:
setAppDefaultConfig = {
extension->
//引用Application插件库
extension.apply plugin:`com.andorid.application`
extension.description "app"
setAndroidConfig extension.android
setDependencies extension.dependencies
//为每个Module配置不同的Application ID
extension.android.defaultConfig{
applicationId applicationId+"."+extension.getName()
}
}
这里的extension.getName()相当于project.getName(),在默认的ApplicationId后面添加Module名字,用于区分不同的Module产生的APP。
3)在src中建立app文件夹
image.png
这里用于放置调试状态需要的AndroidManifest文件和其他在作为Module和Application不同状态下不同的文件。例如在正常状态下MainActivity是这样配置的:
<activity android:name=".MainActivity">
</activity>
但是app下,这个Activity需要作为这个Module的MainActivity启动,那么就可以在app文件夹下新建一个AndroidManifest文件。并添加启动Activity的Intent-filter:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
4)声明需要单独调试的模块。
如果每次需要单独调试一个某块都需要在里面改来改去,那么不但繁琐,而且容易出错。更好的方法是在config.gradle 中声明一个Module是否需要单独调试的变量。例如添加一个isLoginDebug
project.ext {
isLoginDebug = false
}
在模块的build.gradle 中将变量isLoginDebug作为单独应用和lib的开关:
if(project.ext.isLoginDebug){
project.ext.setLibDefaultConfig project //当前为lib
} else{
project.ext.setAppDefaultConfig project //当前为app
}
在sourceSets中配置不同的资源路径和Manifest路径:
sourceSets{
main{
if(project.ext.isLoginDebug){
manifest.srcFile `src/app/AndoridManifest.xml`
res.srcDirs =['src/app/res','src/main/res']
} else {
manifest.srcFile `src/main/AndoridManifest.xml`
resources {
//排除debug下所有文件
exclude `src/app/*`
}
}
}
}
这样就可以在编译构建的时候,根据isLoginDebug来判断选择相应的配置文件和资源。
5)原APP Module需要移除已经单独调试的模块依赖。
dependencies {
if(!project.ext.isLoginDebug){
compile project(':login")
}
}
为什么需要设置移除成为Application的Module呢?这是因为Application只能依赖Library Module,不能依赖 Application Module。所以如果不改,编译无法通过。
3.Gradle 4.1+ 的依赖特性
Gradle升级到4.1以后,原先的compile方式改成了implementation方式。有什么差异呢?implementation不能传递依赖。换句话说就是如果一个Module被以implementation的方式作为一个lib,那么这个lib的主Application是不能访问这个Module的依赖库的。这么说有点抽象。看下下面的架构图:
架构图
使用implementation 的方式,APP层是无法直接调用BaseModule中的类的。这样做有什么好处?当BaseModule中改动的时候,APP层不需要重新编译,只需要编译Base层即可,提高了编译效率。当然实际使用中如果确实需要访问baseModule的依赖库,就需要把implementation改成api,这样的效果就和compile相同。所以BaseModule中的第三方库建议使用api的方式依赖第三方库,虽然这样牺牲了一定的编译效率,但是这样可以减少调用封装的代码,加快开发。
4.Git组件化部署
一、submodule子模块
-
添加子模块到项目中可以使用:
git submodule add module的git地址
然后在setting.gradle中配置Library Module即可。当主工程需要更新最新代码的时候,可使用如下命令:
git submodule update
由于存在缓存的原因,所以不一定能够成功。可以使用git submodule update -remote
从远程仓库获取全部最新代码。 -
工程同步
如果使用了上面配置的submodule方式,首次从主项目clone代码的时候,子工程只有文件夹模块,文件夹中没有任何代码。这时需要git submodule update - init -recursive
来拉取各项子工程代码。如果这时出现“Plugins Suggestion Unknown features (Run configuration [AndroidRunConfigurationType],Facet[android,android-gradle]) covered by disable plugin detected. Enable plugins ... Ignore Unknown Features”。这是因为Android Support没有被勾选导致的,点击File->Setting->Plugins,勾选Android Support,点击 Apply,然后重启。
如果想要删除一个子模块,可以执行以下命令:
git rm -r -cached 子模块名称
rm -rf .git/modules/子模块名称
二、subtree
-
子模块管理也可以使用subtree的方法。
通过git subtree add -prefix=<sub项目相对路径> <sub项目git地址><分支> --squash
关联主项目。一般 -prefix= 后面直接用空格代替,表示直接关联到根目录下。如果需要关联多个子工程,关联下一个子工程前先要使用git commit 命令来提交代码。 -
更新/提交子模块代码
通过git subtree push -prefix=<sub项目相对路径> <sub项目git地址><分支>
可以提交改动到远程仓库。
git subtree pull -prefix=<sub项目相对路径> <sub项目git地址><分支>
从远程仓库更新代码到本地。
至于到底是用submodule还是subtree,目前来看主流的意见是subtree。submodule使用起来相对繁琐些,而且如果每次提交都需要update,如果没有update就直接commit就会覆盖远程仓库的版本,导致冲突。当然两种方法各有各的优缺点,需要结合实际项目需求,这两个方法的优缺点值得再开一遍文章来写。
好了,到这里我们就大致完成了组件化架构一些基本的开发和管理介绍。