4. 了解 Gradle API 兼容性变化
做 Gradle 升级,最头疼的是什么?
不是新功能不会用,而是旧代码突然跑不起来了。说白了,就是 API 变了。
Gradle 团队对 API 的改动其实挺频繁的。我在几次大型升级项目中,都被这些变化坑过。今天咱们就好好捋一捋,哪些 API 容易变,以及怎么应对。
4.1 公共 API 与内部 API 的区别
这里有个概念先搞清楚。
Gradle 把 API 分成了两类:公共 API 和 内部 API。
| 对比维度 | 公共 API (Public API) | 内部 API (Internal API) |
|---|---|---|
| 声明方式 | 标记为 @Incubating 或正式 API | 类/方法上标注 @Internal |
| 稳定性 | 尽量保持兼容性 | 随时可能修改,无任何兼容保证 |
| 推荐使用 | ✅ 强烈推荐 | ❌ 强烈不推荐 |
| 升级风险 | 低(有迁移文档) | 高(突然失效) |
⚠️ 注意:我见过太多团队直接依赖了某个 internal 的类,结果升级后项目直接编译失败。千万别这么干!
4.2 Configuration API 重构(compile → api/implementation)
这是 Gradle 3.4 引入的一个大变化。
以前我们写依赖都是 compile,后来变成了 api 和 implementation。为什么要改?
- compile:所有依赖都会被暴露给消费者(模块),耦合性强,编译慢。
- api:相当于之前的 compile,依赖链会暴露。
- implementation:依赖不会暴露给消费者,编译更快,推荐首选。
举个例子你就明白了:
// 旧版写法(Gradle 4.x 之前)
dependencies {
compile 'com.google.guava:guava:28.0-jre'
compile project(':library-core')
}
// 新版写法(Gradle 7.x)
dependencies {
implementation 'com.google.guava:guava:28.0-jre'
api project(':library-core') // 需要暴露给依赖方的类直接用 api
}
我自己的经验是,升级时直接全局搜索 compile,全部替换成 implementation,然后逐个模块检查是否需要改为 api。这样最稳妥。
4.3 Task 配置 API 的变化
另一个让我印象深刻的变化,是 Task 的配置方式。
老版本里,我们经常这么写:
task myTask {
doLast {
println 'Hello'
}
}
但 Gradle 5.0 之后,更推荐用 TaskProvider 来配置:
def myTask = tasks.register('myTask', MyTaskType) {
// 配置闭包,延迟执行
description = 'My task'
}
// 获取时再执行
myTask.configure {
doLast {
println 'Hello'
}
}
tasks.register() 是延迟创建,比 tasks.create() 性能更好。我个人习惯是,新项目一律用 register()。
4.4 Provider API 与延迟计算
Gradle 5.0 以后,引入了 Provider API。这东西很重要。
说白了,Provider 是一种延迟计算的值。你定义一个 Provider,它不会立即求值,只有在真正用到的时候才会计算。这对于配置阶段性能优化很有帮助。
// 创建 Provider
Provider<String> myProvider = project.providers.provider {
// 这个闭包在访问时才执行
System.currentTimeMillis().toString()
}
// 获取值(延迟执行)
String value = myProvider.get()
为什么需要这个?
- 配置阶段只定义,不执行,避免不必要的计算。
- 支持链式转换,比如
.map()、.flatMap()。 - 更好的配合 Gradle 的构建缓存。
💡 个人建议:在自定义 Task 或 Extension 时,参数尽量用 Property<T> 替代 T 字段。比如用 Property<String> 代替 String。这样能充分利用 Provider API 的延迟特性和懒加载。
4.5 常见的兼容性变化清单
我把升级过程中常遇到的 API 变化整理了一下,方便你对照:
| 旧 API | 新 API | 影响版本 | 说明 |
|---|---|---|---|
compile |
api / implementation |
≥ 3.4 | 依赖配置变更,implementation 更推荐 |
sourceSets.custom.outputDir |
outputs.dir() |
≥ 5.0 | 使用 Provider API 管理输出 |
TaskInputs.source() |
TaskInputs.file() / .dir() |
≥ 4.10 | 更细粒度的输入声明 |
GradleVersion.current() |
无直接替代 | ≥ 6.0 | 该 API 已标记为 internal,不推荐使用 |
🔍 避坑指南:我曾经在升级 Gradle 7.0 时,项目里用了大量 GradleVersion.current() 来做版本判断。结果这个 API 在 7.0 被标记为 internal,直接编译报错。排查了半天才发现是这个问题。
4.6 如何快速定位 API 兼容性问题
说了这么多,那实际升级时怎么快速找到问题?
我总结了三个步骤:
- 跑一次全量构建,加上 --warning-mode=all
./gradlew build --warning-mode=all
这个命令会输出所有使用弃用 API 的警告。你能清楚地看到哪些地方需要改。
- 查看 Gradle 官方 Upgrade Guide
每个大版本都有对应的升级指南。比如从 6.x 到 7.x,官方会列出所有破坏性变化。直接去官网搜 "Gradle 7.x Upgrade Guide" 就行。
- 利用 IDE 的 Inspection 功能
IntelliJ IDEA 里,Gradle 插件会高亮显示已弃用的 API。鼠标悬停还能看到替代方案。这个很方便。
💡 小技巧:升级前先跑一遍 ./gradlew build --warning-mode=all,把输出的警告保存下来。升级后再跑一遍,对比看哪些警告变成了错误。这样心里有数。
4.7 总结一下
Gradle API 的变化是升级路上的绊脚石,但不是无法绕过。
记住几个要点:
- 尽量只用 公共 API,远离 内部 API。
- 多关注 弃用警告,别等到升级了再改。
- 学会用 Provider API,这是未来的主流写法。
嗯,这一节的内容就是这些。搞懂了 API 兼容性,升级的时候就能少踩一半的坑。