本文已参与「新人创作礼」活动,一起开启掘金创作之路。
通过上一篇文章,我们在发现在config.gradle中定义了一个isComponent字段:
// true 组件化环境,将所有业务Library组件化为可执行Module,供开发人员开发
// false 集成环境,将所有可执行Module集成化为Library,打包到App主模块里
isComponent = false
修改这个字段,我们希望当它为true时,表示组件开发环境,将所有Library组件化为可执行Module,供开发人员开发;当它为false时,表示集成发布环境,将所有可执行Module集成化为Library,打包到App主模块里。
前面我们也分析了,对于Library来说,转换为可执行Module,在其build.gradle中我们需要修改两处:
- 1、将‘com.android.library’ 改为 ‘com.android.application’
- 2、添加 applicationId
我们可以通过isComponent字段,在子模块的build.gradle通过代码实现这个过程,如下:
(重点看注释部分)
// 1、取出isComponent字段
def isComponent = rootProject.ext.isComponent
// 2、根据isComponent字段,来确定当前是集成化 还是组件化
if (isComponent) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'kotlin-android'
def appId = rootProject.ext.appId
def application = rootProject.ext.application
def dependenciesImport = rootProject.ext.dependenciesImport
def version_code = rootProject.ext.versionCode
def version_name = rootProject.ext.versionName
android {
compileSdkVersion application.compileSdkVersion
buildToolsVersion application.buildToolsVersion
defaultConfig {
// 3、如果当前是组件化,那么就需要 applicationId
if (isComponent) {
applicationId appId.user
}
minSdkVersion application.minSdkVersion
targetSdkVersion application.targetSdkVersion
versionCode version_code.user
versionName version_name.user
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
dependenciesImport.each { k, v -> implementation(v) }
}
而对于app主模块来说,如果当前是集成化,还需以Library的形式将子模块依赖进来,所以还需修改app主模块的build.gradle文件,如下: (重点看注释部分)
plugins {
id 'com.android.application'
id 'kotlin-android'
}
//1、取出isComponent字段
def isComponent = rootProject.ext.isComponent
def appId = rootProject.ext.appId
def application = rootProject.ext.application
def dependenciesImport = rootProject.ext.dependenciesImport
def version_code = rootProject.ext.versionCode
def version_name = rootProject.ext.versionName
android {
compileSdkVersion application.compileSdkVersion
buildToolsVersion application.buildToolsVersion
defaultConfig {
applicationId appId.app
minSdkVersion application.minSdkVersion
targetSdkVersion application.targetSdkVersion
versionCode version_code.app
versionName version_name.app
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
dependenciesImport.each { k, v -> implementation(v) }
//2、当前是发布环境,那么需要引入其它子模块的Library
if (!isComponent) {
implementation project(path: ':user')
}
}
我们到时候可以根据是组件化开发还是集成化来更改isComponent字段,然后重新sync now,即可实现组件化与集成化的切换;
组件化与集成化转化的问题解决后,run一下,你会发现一个现象,屏幕上出现两个甚至多个app图标。
这是为什么呢? 这是因为个开发人员在组件化环境下进行开发时,对各自模块的module中的AndroidManifest.xml中application以及UserMainActivity配置了logo和Launch入口;而在集成化过程中,各模块AndroidManifest.xml合并为按一个文件,最终导致产生了两个甚至多个程序入口。
那么,按照这个思路,我的实现方案如下:
- 1、在子模块的main文件夹下新建_ReleaseManifest文件夹;
- 2、拷贝一份AndroidManifest.xml到该文件夹下,并删除logo以及Launch入口相关代码;
- 3、在子模块build.gradle中根据isComponent字段来指定对应的AndroidManifest.xml文件。
测试下,嗯,没问题。但仔细想想,AndroidManifest.xml文件是我们开发过程中需要经常修改的文件,而现在就需要修改两次,或者说每次切换至集成环境都需同步一次。这样未免过于繁琐,而且手动同步也极易出错,怎么办?
交给Gradle!在编译期,通过脚本来实现拷贝及删除工作,比起人工往往更安全且高效,相关的代码实现我也贴在了下方,核心思路还是和上面一样,只需:
- 1、在项目根目录创建manifestRelease.gradle文件,并粘贴下方代码:
// manifestRelease.gradle 文件内容
import groovy.xml.XmlUtil
def log(String moduleName, String info) {
println("<$moduleName> ===> $info")
}
def manifestRelease(String moduleName) {
//==================Start (集成化AndroidManifest)=====================
//找到这个模块的路径
String originDir = project(moduleName).projectDir
//copy AndroidManifest
def releaseManifestDir = "${originDir}/src/main/_ReleaseManifest"
copy() {
from "${originDir}/src/main/AndroidManifest.xml"
into releaseManifestDir
}
//删除不需要的属性
def releaseManifestFile = "${releaseManifestDir}/AndroidManifest.xml"
def parser = new XmlParser(false, false)
def releaseManifestXml = parser.parse(releaseManifestFile)
//删除application中的属性
releaseManifestXml.application.each { application ->
def keys = application.attributes().keySet()
def newKeyList = new ArrayList(keys)
newKeyList.forEach {
def attrStr = it.toString()
// application 需要的属性保留在这里
def filter = (attrStr.contains('android:allowBackup')
|| attrStr.contains('android:supportsRtl')
|| attrStr.contains('android:theme'))
if (!filter) {
log(moduleName, "remove application attributes :: ${it}")
application.attributes().remove(it)
}
}
application.attributes().keySet().forEach {
log(moduleName, "has application attributes :: ${it}")
}
//删除 LAUNCHER <intent-filter>
def categoryList = releaseManifestXml.application.activity.'intent-filter'.category
log(moduleName, categoryList.toString())
categoryList.forEach { category ->
def categoryName = category.attributes().get('android:name')
if (categoryName == 'android.intent.category.LAUNCHER') {
def intent_filter = category.parent()
if (intent_filter.name() == 'intent-filter') {
def delResult = intent_filter.parent().remove(intent_filter)
log(moduleName, "del android.intent.category.LAUNCHER for intent-filter :: $delResult")
}
}
}
//保存
PrintWriter pw = new PrintWriter(releaseManifestFile, ("UTF-8"))
pw.write(XmlUtil.serialize(releaseManifestXml))//用XmlUtil.serialize方法,将String改为xml格式
pw.close()
}
//==================End (集成化AndroidManifest)=====================
}
ext {
manifestRelease = this.&manifestRelease
}
- 2、使用时,和导入config.gradle类似,首先在项目根目录的build.gradle中导入脚本:
// 根目录中的build.gradle文件内容
apply from: "config.gradle"
// 导入我们编写的manifestRelease脚本
apply from: "manifestRelease.gradle"
buildscript {
...
- 3、最后,在子模块的build.gradle中调用脚本函数即可:
// user子模块的build.gradle文件内容
def isComponent = rootProject.ext.isComponent
if (isComponent) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'kotlin-android'
...
// 调用清单文件处理函数
rootProject.ext.manifestRelease(project.name)
android {
compileSdkVersion application.compileSdkVersion
...
来看看最后的效果吧!!!