依赖版本管理:版本目录详解、版本冲突解决、强制版本、依赖约束、平台依赖
依赖版本管理,说白了就是管好你项目里那些第三方库的版本号。我见过太多项目,因为版本号写得到处都是,升级一个库要改十几个文件,最后干脆放弃治疗。嗯,今天我们就来彻底解决这个问题。
版本目录(Version Catalog)—— 集中管理版本号
Gradle 7.0 之后引入了版本目录,我个人觉得这是近年来最好的改进之一。以前我们习惯在 ext 块里定义版本号,但那种方式不够结构化,IDE 支持也差。
版本目录的核心思想:把所有依赖的坐标和版本号,统一放到 libs.versions.toml 文件里管理。
libs.versions.toml 文件结构
[versions]
kotlin = "1.9.0"
compose-bom = "2024.02.00"
retrofit = "2.9.0"
[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
[bundles]
compose = ["compose-bom", "compose-ui", "compose-material3"]
[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
然后在 build.gradle.kts 里这样引用:
dependencies {
implementation(libs.kotlin.stdlib)
implementation(platform(libs.compose.bom))
implementation(libs.bundles.compose)
}
你想想看,这样改版本号只需要改一个地方。我在项目中遇到过团队里有人直接在 dependencies 块里写死版本号,结果升级时漏了一个,编译报错查了半天。用了版本目录之后,这种问题再也没出现过。
小技巧:版本目录文件默认放在 gradle/ 目录下。如果你用多模块项目,所有模块共享同一个版本目录,爽得很。
版本冲突解决 —— Gradle 的自动仲裁机制
依赖冲突是 Android 开发者的老朋友了。比如你依赖了 A 库和 B 库,它们各自依赖了不同版本的 C 库,这时候 Gradle 怎么选?
Gradle 默认的策略是:选择最高版本。这听起来合理,但有时候会出问题。我曾经遇到过一个坑:A 库依赖了 OkHttp 3.12,B 库依赖了 OkHttp 4.0,Gradle 自动选了 4.0。结果 A 库内部用了 OkHttp 3.x 的 API,4.0 里被标记为废弃了,运行时直接崩溃。
怎么排查?用这个命令:
./gradlew :app:dependencies --configuration implementation
它会输出一棵依赖树,你能清楚看到每个库的版本是怎么来的。
注意:依赖树输出可能很长,建议用 --scan 参数生成 Build Scan 网页,可视化查看。
强制版本 —— 我说了算
有时候 Gradle 的自动仲裁结果不是你想要的。比如你明确知道某个版本有问题,想强制项目里所有地方都用另一个版本。这时候可以用 force。
configurations.all {
resolutionStrategy {
force("com.squareup.okhttp3:okhttp:4.11.0")
}
}
或者更细粒度地,在依赖声明里加:
implementation("com.squareup.okhttp3:okhttp:4.11.0") {
force = true
}
我个人习惯尽量少用 force,因为它相当于「暴力覆盖」,可能会让某些库的预期行为失效。但有些场景确实绕不开,比如安全补丁升级。
依赖约束 —— 更优雅的版本控制
依赖约束是 Gradle 6.0 之后引入的,比 force 更温和。它告诉 Gradle:「我建议用这个版本,但如果其他库有更严格的版本要求,可以覆盖我。」
dependencies {
constraints {
implementation("com.squareup.okhttp3:okhttp:4.11.0") {
because("修复了 CVE-2023-XXXXX 安全漏洞")
}
}
}
依赖约束的好处是:它不会像 force 那样强行覆盖,而是参与版本仲裁。如果另一个库要求必须用 4.10.0,Gradle 会选 4.11.0 和 4.10.0 中更高的那个——也就是 4.11.0。但如果那个库要求 strictly 4.10.0,那约束就失效了。
依赖约束 vs 强制版本
| 特性 | 依赖约束 | 强制版本 |
|---|---|---|
| 优先级 | 参与仲裁,可被覆盖 | 最高优先级,不可覆盖 |
| 适用场景 | 建议性版本升级 | 必须使用某个版本 |
| 风险 | 低 | 高,可能破坏其他库 |
平台依赖 —— BOM 的妙用
平台依赖(Platform Dependency)是解决「一组库必须用相同版本」问题的利器。最典型的例子就是 Android Compose 和 Spring Boot。
Compose 的 BOM(Bill of Materials)用法:
dependencies {
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
// 不用写版本号,BOM 统一管理
}
你想想看,如果没有 BOM,Compose 的 ui、material3、animation 等十几个库的版本号你得一个一个写,升级时还得保证它们互相兼容。有了 BOM,一个版本号搞定所有。
自定义平台依赖也很简单:
dependencies {
implementation(platform("com.example:my-bom:1.0.0"))
}
只要你的 BOM 里用 dependencyManagement 声明了版本号,其他库引用时就可以省略版本。
经验之谈:我建议每个 Android 项目都引入 Compose BOM,哪怕你还没用 Compose。因为 BOM 里还管理了 androidx.core:core-ktx、androidx.lifecycle 等基础库的版本,省心不少。
知识体系总览
下面这张图总结了本章的核心内容,你可以把它当作一个快速参考:
这张图把五个核心概念串起来了。版本目录是基础,帮你管好版本号;版本冲突解决是日常必备技能;强制版本和依赖约束是进阶手段,一个暴力一个温和;平台依赖则是批量管理的最佳实践。
最后说一句:版本管理没有银弹。我见过有人把所有依赖都 force 到最新版,结果项目编译不过;也有人完全不管版本冲突,等到运行时崩溃才去查。我的建议是:先用版本目录打好基础,遇到冲突时优先用依赖约束,实在不行再用 force。BOM 能用的地方尽量用,省心省力。