4. 多模块项目统一版本策略:subprojects块、版本目录libs.versions.toml
4.1 原理:为什么需要统一版本
在大型 Android 多模块项目中,每个子模块都可能声明 org.jetbrains.kotlin:kotlin-stdlib、androidx.compose.compiler:compiler 等依赖。若各自手动硬编码版本号,极易产生以下问题:
- 版本冲突:子模块 A 使用 Kotlin 1.8.0,子模块 B 使用 1.8.10,导致编译时难以排查的二义性问题。
- 升级成本高:需要搜索所有
.gradle/.gradle.kts文件逐个替换,易遗漏。 - Compose Compiler 不兼容:Compose Compiler 必须与 Kotlin 版本严格匹配(详见第 2 章),分散管理极易导致装配合成插件失败。
subprojects 块 是 Gradle 在多模块根项目 build.gradle.kts 中用于统一配置所有子模块的声明区域。版本目录 (Version Catalog) 是 Gradle 7.0+ 官方推荐的依赖版本集中管理机制,通过 libs.versions.toml 文件统一声明版本/依赖/插件,支持类型安全访问。
核心理念:一处定义,处处引用;subprojects 保证配置传递,版本目录保证版本唯一。
4.2 实现流程
- 创建版本目录文件:在项目根目录创建
gradle/libs.versions.toml。 - 定义版本与依赖:在
[versions]块中声明 Kotlin 和 Compose Compiler 版本;在[libraries]块中声明具体依赖坐标。 - 配置 subprojects 块:在根
build.gradle.kts中遍历所有子模块,应用公共配置,包括 Kotlin 插件版本、Compose Compiler 插件版本。 - 子模块引用:子模块通过
libs.kotlin.stdlib等类型安全访问器获取依赖,不再出现硬编码版本。 - 验证一致性:运行
gradle dependencies检查依赖树上版本是否统一。
4.3 关键配置示例
gradle/libs.versions.toml
[versions]
kotlin = "1.9.22"
compose-compiler = "1.5.11"
compose-bom = "2024.02.00"
[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose-bom" }
[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
根 build.gradle.kts(subprojects 块)
plugins {
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.compose.compiler) apply false
}
subprojects {
apply(plugin = "org.jetbrains.kotlin.android")
apply(plugin = "org.jetbrains.kotlin.plugin.compose")
dependencies {
"implementation"(libs.kotlin.stdlib)
"implementation"(platform(libs.compose.bom))
"implementation"(libs.compose.ui)
}
// 可选:统一 Android 编译配置
android {
compileSdk = 34
defaultConfig {
minSdk = 26
targetSdk = 34
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
}
子模块 build.gradle.kts(简洁引用)
plugins {
// 不再重复声明 Kotlin 和 Compose 插件版本
id("com.android.library")
}
dependencies {
// 类型安全访问器,自动匹配版本目录
implementation(libs.kotlin.stdlib)
implementation(libs.compose.ui)
}
注意:使用
alias(libs.plugins.xxx) 时必须在根 build.gradle.kts 的 plugins {} 块中先 apply false,否则子模块会报插件重复注册。
4.4 版本统一结构图
4.5 调试:验证版本统一
运行下列命令查看编译期实际使用的 Kotlin 和 Compose Compiler 版本:
# 查看所有子模块的依赖树,确认 kotlin-stdlib 版本一致
./gradlew :app:dependencies --configuration implementation
# 检查 Compose Compiler 插件版本
./gradlew :feature:home:dependencies --configuration kotlinCompilerPluginClasspath
若输出显示多个不同的 Kotlin/Compose Compiler 版本,说明版本目录配置未正确覆盖所有子模块,需检查 subprojects 块是否遗漏了特定模块。
4.6 常见错误与解决方案
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
子模块报错 Plugin with id 'org.jetbrains.kotlin.android' not found |
子模块未继承 root 的 buildscript 或 plugins {} 块未使用 alias |
在根 build.gradle.kts 的 subprojects {} 中显式 apply(plugin = "org.jetbrains.kotlin.android") |
| 编译时 Kotlin 版本不一致警告 | 某个子模块在 build.gradle.kts 中直接硬编码了 kotlin("stdlib") version "1.9.20" |
移除所有硬编码版本号,统一使用 libs.kotlin.stdlib |
类型安全访问器 libs.kotlin.stdlib 无法解析 |
libs.versions.toml 中 [libraries] 的名称包含非法字符或未定义 |
确认目录文件位于 gradle/libs.versions.toml;检查命名规范(只允许字母、数字、短横、点) |
4.7 性能与稳定性优化
- 减少配置时间:
subprojects块中避免写入每次构建都会变更的内容(如动态版本)。将不变配置(如 Compile SDK)集中管理,Gradle 的配置缓存更有效。 - 避免重复解析版本目录:版本目录文件只会在根项目加载一次,子模块通过
libs访问器获取依赖,不增加额外 I/O。 - 渐进式升级:当需要升级 Kotlin 时,仅修改
libs.versions.toml中的kotlin和compose-compiler两个版本号,然后通过--write-locks重新生成依赖锁定文件,确保所有子模块同步。
警惕循环引用:在
subprojects 块内不要调用 project.evaluationDependsOnChildren(),否则会与版本目录的延迟解析产生死锁。若需要跨模块配置,优先使用 afterEvaluate。