上篇博客Android使用Gradle插件统一组件化架构各组件的版本(上)遗留了两个问题,今天抽空又去研究了下, 还没看过上篇博客的先去了解下再来看这篇。
- 单Module想要以App形式运行
- Module源码和Aar之间的切换,或者Module源码和maven之间的切换
问题1
随着项目越做越大,可能后期拆分的业务Module越来越多,随之编译速度会变慢,但是其实我们增加需求还是改需求一次不可能涉及到很多个Module代码的同时修改,
那么我们是不是可以改哪个业务层就只编译那个业务层的Module呢,其他业务层不参与编译。
目前这个问题目前已经完美解决了,只需要一个boolean变量即可切换Library和Application
源码,这次代码在分支test,建议下载源码体验一下
本文主要讲相比上篇改动较大的地方
LibsBuildPlugin
package com.tools.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.PluginContainer
/**
* @author: cb
* @date: 2023/10/12
* @desc: 统一多Module中Gradle的配置
*/
class LibsBuildPlugin : Plugin<Project> {
override fun apply(target: Project) {
try {
with(target) {
var isModuleApp = false
try {
//需要注意这里key用了module的名称+(_app),例如:proj_search_app
// 在gradle.properties定义
val key = "${project.name}_app"
isModuleApp = providers.gradleProperty(key).get() == "true"
} catch (e: Exception) {
}
println("isApp=$isModuleApp")
//判断是否需要app单独运行
if (isModuleApp) {
plugins.apply {
apply("com.android.application")
addCommonPluginContainer(this)
}
} else {
plugins.apply {
apply("com.android.library")
addCommonPluginContainer(this)
}
}
//根据不同配置,走App还是Library
if (isModuleApp) {
configModuleApp(extensions, "${project.name}.application.id")//后面会讲
} else {
configLibrary(extensions)//后面会讲
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 添加公共的插件
* @param pluginContainer PluginContainer
*/
private fun addCommonPluginContainer(pluginContainer: PluginContainer) {
pluginContainer.apply("kotlin-android")
pluginContainer.apply("kotlin-parcelize")
}
}
主要注意isModuleApp这个boolean值走的不同的if else
providers.gradleProperty(key).get() == "true"
这段代码主要取项目根目录中的gradle.properties里面定义的boolean值,假如我们的proj_home Module,那就需要再gradle.properties中定义proj_home_app=true,详细代码gradle.properties,只需要一个变量就可以切换Library和Application
PlugExt.kt
package com.tools.plugin
import ProjectVersion
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.DefaultConfig
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.JavaVersion
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
/**
* module以单独app进行调试,可以理解成单元测试
* @param ex ExtensionContainer
*/
fun configModuleApp(ex: ExtensionContainer, appId: String?) {
configApp(ex, appId ?: "com.default.app", true)
}
/**
* 真实的app,也就是最终合并了所有module的app
*/
fun configRealApp(ex: ExtensionContainer) {
configApp(ex, null, false)
}
/**
* App配置
* @param ex ExtensionContainer
* @param appId String? 单独module需要指定包名
* @param isModuleApp Boolean 是否是module形式的app
*/
private fun configApp(ex: ExtensionContainer, appId: String?, isModuleApp: Boolean) {
ex.configure<ApplicationExtension> {
compileSdk = ProjectVersion.compileSdk
defaultConfig {
//如果业务module以app形式单独运行,需要设定一个包名
if (appId != null) {
applicationId = appId
}
minSdk = ProjectVersion.minSdk
targetSdk = ProjectVersion.targetSdk
versionCode = ProjectVersion.versionCode
versionName = ProjectVersion.versionName
vectorDrawables.useSupportLibrary = true
commonDefaultConfig(this)
}
sourceSet(this, isModuleApp)
//Application和library相同的配置
unifiedConfiguration(this)
}
}
/**
* 设置资源目录
* @param extension ApplicationExtension
* @param isModuleApp Boolean
*/
private fun sourceSet(extension: ApplicationExtension, isModuleApp: Boolean) {
extension.sourceSets {
getByName("main") {
if (isModuleApp) {
//根据实际情况设置单独module app的AndroidManifest路径
manifest.srcFile("src/app/AndroidManifest.xml")
java {
srcDirs("src/app")//重点
srcDirs("src/main/java")
}
res {
srcDirs("src/main/res")//重点
srcDirs("src/app/res")
}
} else {
manifest.srcFile("src/main/AndroidManifest.xml")
}
}
}
}
//省略代码。。。。。。。。
主要是configApp()、sourceSet()两个方法
configApp()
如果Module需要单独运行肯定需要需要设置ApplicationId也就是包名,为了不和源App冲突,这里可以随便填写,反正是开发自己测试用,demo中使用 configModuleApp(extensions, "${project.name}.application.id")也就是Module的名称来作为包名
sourceSet()
如果Module需要单独运行肯定需要需要设置logo、app_name、AndroidManifest等等,所以sourceSet()主要就是做这件事情的,不想每个Module都写一份,所以在插件里统一处理了;
需要注意的就是目录要对应上哦,比如
java {
srcDirs("src/app")//重点
srcDirs("src/main/java")
}
目录要对应上源码位置
如上图proj_search在gradle.properties中设置proj_home_app=true,可以看到Android Studio已经扫描并加载src/app目录里面的java代码和res和AndroidManifest
如需打包正式Release包时记得将gradle.properties中值设置为false,不然打不了包会报错,src/app目录里面的class、资源、AndroidManifest打正式包的时候不会打包进APK的,这点放心。
验证编译速度
所有Module都参与打包的情况
大概23秒
单独运行proj_home
6秒,这差距很明显吧,两次测试都是clear后编译的哦,大大提升的开发效率,下载源码自己亲身体验下吧。
问题2
需求场景:比如App更新相关的Module可能长时间都不会修改,但是每次clear后都会参与编译,会增加我们的编译耗时,这个时候我们就可以把更新Module打包成Aar或者推到公司的maven库;
好处
- 提高编译速度
- 稳定性,App更新这种比较重要的功能,保证组内开发改其他需求的时候不会修改到相关的代码
其实跟问题1类似的,就是一个变量来控制源码和Aar之间的切换;
源码分支 需要切换到aar分支;
这里不展开细说了,下载源码,切换分支,看这个提交记录的修改
这里需要注意的就是,Aar需要提前打出来放到需要引用的Module中的libs目录下,然后再将gradle.properties中切换开关打开即可。
这里主要为了演示,只操作的Aar源码之间的切换,maven库没有所以这里不展示了