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 实现流程

  1. 创建版本目录文件:在项目根目录创建 gradle/libs.versions.toml。
  2. 定义版本与依赖:在 [versions] 块中声明 Kotlin 和 Compose Compiler 版本;在 [libraries] 块中声明具体依赖坐标。
  3. 配置 subprojects 块:在根 build.gradle.kts 中遍历所有子模块,应用公共配置,包括 Kotlin 插件版本、Compose Compiler 插件版本。
  4. 子模块引用:子模块通过 libs.kotlin.stdlib 等类型安全访问器获取依赖,不再出现硬编码版本。
  5. 验证一致性:运行 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 版本统一结构图

多模块版本统一流程:版本目录+subprojects 根 build.gradle.kts libs.versions.toml subprojects { ... } :feature:home libs.kotlin.stdlib :feature:profile libs.compose.ui :core:network libs.kotlin.stdlib 统一收益 Compose Compiler 版本与 Kotlin 版本仅在一处定义 所有子模块自动继承,升级只需修改 libs.versions.toml

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。