今天给大家介绍一种组件化架构,统一各个Module中的版本号、相似代码的、配置一种方案。
现在基本上中大型项目都是采用组件化架构,随着项目中的Library Module不断增多,问题也不断增多,下面来看看常见的一些问题
- 不同
Module的minSdk、targetSdk、versionCode、versionName统一版本号。 - 不同
Module中的build.gradle有大量重复代码。 dependencies三方库的版本不统一- 向所有
Module中的BuildConfig中添加静态常量麻烦 - aar和源码切换或者单
Module运行增加编译速度
本文使用Gradle插件+build.gradle.kts ,前提需要有点基础,还在使用Groovy的可以先了解下kts
组件化架构
组件化架构、组件拆分、组件解耦、组件通信等等不是本文重点,下面引用京东的博客,不懂组件如何拆分的可以参考这个博客
新建一个工程
整体介绍一下项目工程
- app:App的宿主,可以理解就是一个壳,一般是
Application和启动页 - layer_base:项目基础层,一般存放工具类等等
- layer_common:业务公共层,放一些多个
project层共用的代码 - layer_project:业务层,一般就是具体的业务,比如,首页,搜索页,播放器等等
- layer_extends:业务扩展层,一般是跟项目无关的三方库,如ijk,友盟,等等
- version-plugin:是一个
Gradle插件,用于统一项目配置,本文核心就是这里
插件
ProjectVersion
import java.util.Date
object ProjectVersion {
const val compileSdk = 33
const val targetSdk = 33
const val minSdk = 19
const val versionCode = 100
const val versionName = "1.0.0"
const val applicationId = "com.example.project"
/**
* 项目接口的地址
*/
const val url = "http://baidu.com"
/**
* 定义参数
*/
var param = false
//可以根据项目需求定义更多的变量.......
/**
* 获取打包时间
* @return String
*/
fun getCurrDate(): String {
val date = Date()
val dateStr = date.time
return "$dateStr"
}
}
主要是一些build.gradle中涉及到的版本号,在这里统一定义。
Dependencies.kt
object Versions {
const val kotlin = "1.8.0"
const val lifecycle = "2.6.1"
const val junit = "4.13.2"
const val androidxJunit = "1.1.3"
const val espresso = "3.4.0"
const val kotlinxCoroutinesVersion = "1.6.4"
const val roomVersion = "2.5.0"
const val okhttpVersion = "3.12.0"
const val routerVersion = "1.2.2"
const val exoVersion = "1.1.1"
}
object Libraries {
//Kotlin
const val coreKtx = "androidx.core:core-ktx:${Versions.kotlin}"
const val kotlinxCoroutinesAndroid =
"org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.kotlinxCoroutinesVersion}"
const val kotlinxCoroutinesCore =
"org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinxCoroutinesVersion}"
//Kotlin Ktx
const val fragmentKtx = "androidx.fragment:fragment-ktx:1.6.0"
const val activityKtx = "androidx.activity:activity-ktx:1.6.0"
const val collectionKtx = "androidx.collection:collection-ktx:1.3.0"
//AndroidX
const val appcompat = "androidx.appcompat:appcompat:1.5.0"
const val material = "com.google.android.material:material:1.9.0"
const val annotation = "androidx.annotation:annotation:1.7.0"
//省略代码。。。。。。。。。。。。。
}
主要是依赖三方库的地方,Versions可以统一版本号,
Extension.kt
import org.gradle.kotlin.dsl.DependencyHandlerScope
fun DependencyHandlerScope.kotlinCore() {
"api"(Libraries.coreKtx)
"api"(Libraries.kotlinxCoroutinesAndroid)
"api"(Libraries.kotlinxCoroutinesCore)
}
fun DependencyHandlerScope.kotlinKtx() {
"api"(Libraries.fragmentKtx)
"api"(Libraries.activityKtx)
"api"(Libraries.collectionKtx)
}
//省略代码。。。。。。。。。。。。。
主要是为了将依赖库分组,比如kotlin相关的,可以用kotlinCore方法归类到一个分组,在Libray Module中引用的时候直接引用kotlinCore();如下面代码
dependencies {
//对应Extension.kt
kotlinCore()
lifecycle()
kotlinKtx()
commonView()
androidTest()
//当然也可以引入单个库
api(Libraries.viewpager2)
}
这样就可以简化很多代码,为了防止组件之间相互引用,就需要实际项目经验来使用implementation和api了,当然这里也可以点进去(windows是ctrl+左键)
LibsBuildPlugin
package com.tools.plugin
import ProjectVersion
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
/**
* @author: cb
* @date: 2023/10/12
* @desc: 统一多Module中Gradle的配置
*/
class LibsBuildPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
//配置library plugin,可以统一添加一些插件
plugins.run {
apply("com.android.library")
apply("kotlin-android")
apply("kotlin-parcelize")
}
//配置android{},注意这里是LibraryExtension
extensions.configure<LibraryExtension> {
compileSdk = ProjectVersion.compileSdk
defaultConfig {
minSdk = ProjectVersion.minSdk
consumerProguardFiles("consumer-rules.pro")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
commonDefaultConfig(this)//后面会讲
}
//Application和library相同的配置
unifiedConfiguration(this)
}
}
}
}
是一个Library Module的Gragle插件,拿到Project后可以调用其内部的方法,在这里可以解决问题1,2,3
AppBuildPlugin
package com.tools.plugin
import ProjectVersion
import com.android.build.api.dsl.ApplicationExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
/**
* @author: cb
* @date: 2023/10/12
* @desc: 统一多Module中Gradle的配置
*/
class AppBuildPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
//配置app plugin,可以统一添加一些插件
plugins.run {
apply("com.android.application")
apply("kotlin-android")
apply("kotlin-parcelize")
}
//配置android{},注意这里是ApplicationExtension
extensions.configure<ApplicationExtension> {
compileSdk = ProjectVersion.compileSdk
defaultConfig {
minSdk = ProjectVersion.minSdk
targetSdk = ProjectVersion.targetSdk
versionCode = ProjectVersion.versionCode
versionName = ProjectVersion.versionName
vectorDrawables.useSupportLibrary = true
commonDefaultConfig(this)//后面会讲
}
lint { baseline = file("lint-baseline.xml") }
//签名信息
signingConfigs {
create("release") {
storeFile = file("../app/keystore.jks")
storePassword = "123456"
keyAlias = "key0"
keyPassword = "123456"
}
}
//Application和library相同的配置
unifiedConfiguration(this)
}
}
}
}
AppBuildPlugin和LibsBuildPlugin内容差不多,这里有差异的就是App对应LibraryExtension,library对应ApplicationExtension,而两者相同的代码可以统一到一个方法中commonDefaultConfig(this)。
签名配置不放这里也没关系,放到app build.gradle中也是可以的,看你的心情
PlugExt.kt
ApplicationExtension继承CommonExtension
LibraryExtension也是继承CommonExtension
所以在这里将App和Library中相同的代码统一处理
package com.tools.plugin
import ProjectVersion
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.DefaultConfig
import org.gradle.api.JavaVersion
import org.gradle.api.plugins.ExtensionAware
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
/**
* 统一app和lib中的公共配置项
* @param ex CommonExtension<*, *, *, *>
*/
fun unifiedConfiguration(ex: CommonExtension<*, *, *, *>) {
ex.buildTypes {
getByName("release") {
isMinifyEnabled = true
}
getByName("debug") {
isMinifyEnabled = false
}
}
ex.compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
ex.viewBinding {
enable = true
}
ex.kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
ex.packagingOptions {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
/**
* buildConfigField 可以在BuildConfig中定义一些静态常量,所有的module中都有
* @param config DefaultConfig
*/
fun commonDefaultConfig(config: DefaultConfig) {
//接口地址
config.buildConfigField("String", "BASE_URL", ""${ProjectVersion.url}"")
//添加参数到所以module的BuildConfig
config.buildConfigField("boolean", "PARAM", "${ProjectVersion.param}")
//可以任意定义.....
}
fun CommonExtension<*, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
(this as ExtensionAware).extensions.configure("kotlinOptions", block)
}
BuildConfig中定义一些静态常量这个需求在我们项目中经常会用到,如果项目Module有很多,不可能每一个module都去写一个,所以这里统一在这里处理了解决了问题4,当然之前使用Groovy时,使用ext{}也是可以实现的。
其他的没什么说的,就是一些经常用到的东西,如viewBinding,混淆配置等等,CommonExtension中还有很多配置可以根据自己的项目需求定义,这里不在多介绍了。
配置插件
插件里的settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = "version-plugin"
include (":version-plugin")
插件里的build.gradle.kts
plugins {
`kotlin-dsl`
}
gradlePlugin {
//定义gradle插件
plugins{
register("AppGradlePlugin") {
//app插件的id
id = "example.app.plugin"
//插件完整的包名类名
implementationClass = "com.tools.plugin.AppBuildPlugin"
}
register("LibsBuildGradlePlugin") {
//library插件的id
id = "jx.libs"
//插件完整的包名类名
implementationClass = "com.tools.plugin.LibsBuildPlugin"
}
}
}
dependencies {
//引入gradle
implementation("com.android.tools.build:gradle:7.4.2")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
}
在这里定义了两个插件App插件和Library插件
注意:定义插件的完整的包名类名不能错需要匹配
引入version-plugin
在项目的settings.gradle.kts中引入
pluginManagement {
includeBuild("version-plugin")
//省略代码。。。。。。。
}
至此插件代码已经ok了
在Library中使用插件
plugins {
//引入我们自己定义的Library插件
id("jx.libs")
}
android {
namespace = "com.example.util"
defaultConfig {
}
}
dependencies {
//对应Extension.kt
kotlinCore()
lifecycle()
kotlinKtx()
commonView()
androidTest()
}
代码是不是很简洁,如需改动配置并应用到所有Library Module直接修改Library插件就可以了
在App中使用插件
plugins {
//对应com.tools.plugin.AppBuildPlugin
id("example.app.plugin")
id("com.google.devtools.ksp")
id("therouter")
}
android {
namespace = ProjectVersion.applicationId
defaultConfig {
applicationId = ProjectVersion.applicationId
ndk {
abiFilters.add("armeabi-v7a")
}
}
buildTypes {
//可以重写,覆盖插件中定义的
getByName("debug") {
isDebuggable = true
isMinifyEnabled = false
}
getByName("release") {
isDebuggable = false
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("release")
}
}
//省略代码。。。。。。
}
dependencies {
router()
implementation(project(":common_core"))
implementation(project(":proj_home"))
implementation(project(":proj_search"))
}
如果插件和build.gradle.kts有相同配置,会覆盖插件中的公共配置
至此本文所有核心内容都已经讲完了我们编译运行一下
随便找个module看下BASE_URL和PARAM都已经添加进来了
我这里简单简单创建了几个Module演示组件化架构,需要的小伙伴可以clone源码参考,路由框架使用的是货拉拉Therouter
ARouter不支持AGP8.0并且长时间不维护了
问题5
2024年8月29日10:29:11
缺点或者使用中遇到的一些问题
- 修改插件代码后
Android Studio不会提示需要同步,需要我们自己去同步一次 - 修改基础层
Module时编译耗时 - 后续补充。。。。。。。。。。。。。。。
总结
本文提供一种思路,用Gradle插件来统一版本号和公共的一些配置
如需在项目中使用要全面了解后再使用,需要权衡各个方面,就比如Kts语法和Groovy语法之间的差异、Gradle版本之间的差异等等都有可能导致项目报错运行不起来。
老项目建议不要轻易尝试,新项目可以使用起来,