4. 了解 Gradle API 兼容性变化

做 Gradle 升级,最头疼的是什么?

不是新功能不会用,而是旧代码突然跑不起来了。说白了,就是 API 变了。

Gradle 团队对 API 的改动其实挺频繁的。我在几次大型升级项目中,都被这些变化坑过。今天咱们就好好捋一捋,哪些 API 容易变,以及怎么应对。

Gradle API 兼容性变化知识体系 Gradle API 兼容性变化知识体系 API 兼容性变化 公共 API(Public API) 弃用标记(@Deprecated) 内部 API(Internal API) ① 方法签名变更 如:Task.onlyIf() 参数变更 ② 返回类型变更 如:getOutputs() 返回类型 ③ 配置方法废弃 如:compile → api/implementation ✅ 使用公共 API 替代 避免直接依赖 internal 类 📋 关注 Deprecation 日志 升级前跑 --warning-mode=all 🔧 使用新 API 适配 如:Provider API 替换旧方法 核心原则:公共 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 兼容性问题

说了这么多,那实际升级时怎么快速找到问题?

我总结了三个步骤:

  1. 跑一次全量构建,加上 --warning-mode=all
./gradlew build --warning-mode=all

这个命令会输出所有使用弃用 API 的警告。你能清楚地看到哪些地方需要改。

  1. 查看 Gradle 官方 Upgrade Guide

每个大版本都有对应的升级指南。比如从 6.x 到 7.x,官方会列出所有破坏性变化。直接去官网搜 "Gradle 7.x Upgrade Guide" 就行。

  1. 利用 IDE 的 Inspection 功能

IntelliJ IDEA 里,Gradle 插件会高亮显示已弃用的 API。鼠标悬停还能看到替代方案。这个很方便。

💡 小技巧:升级前先跑一遍 ./gradlew build --warning-mode=all,把输出的警告保存下来。升级后再跑一遍,对比看哪些警告变成了错误。这样心里有数。

4.7 总结一下

Gradle API 的变化是升级路上的绊脚石,但不是无法绕过。

记住几个要点:

  • 尽量只用 公共 API,远离 内部 API。
  • 多关注 弃用警告,别等到升级了再改。
  • 学会用 Provider API,这是未来的主流写法。

嗯,这一节的内容就是这些。搞懂了 API 兼容性,升级的时候就能少踩一半的坑。