Gradle构建失败:版本兼容性、依赖冲突、JVM与Daemon排查
Gradle 构建失败,算是 CI 流水线上最常见的“绊脚石”了。我个人做了这么多年 Android CI,几乎每个项目都会遇到那么几次。说白了,大部分问题就集中在四个地方:版本兼容性、依赖冲突、JVM 内存不够、Daemon 崩溃。嗯,今天我们就聊聊这四个“老熟人”。
1. Gradle 版本兼容性
这个问题我印象特别深。有一次升级 AGP(Android Gradle Plugin)从 4.2 直接跳到 7.0,结果 CI 上全是 Unsupported class file major version 的错误。为啥?因为我用的 Gradle 7.0 不配 AGP 7.0 的「官方匹配」——AGP 7.0 要求 Gradle 7.0+,但我的 JDK 版本没跟上。后来查官方兼容性表才发现,AGP 和 Gradle 版本是严格绑定的。
| AGP 版本 | 最低 Gradle 版本 | 推荐 JDK 版本 |
|---|---|---|
| 7.0 | 7.0 | 11+ |
| 7.1 | 7.1 | 11+ |
| 7.2 | 7.3 | 11+ |
| 7.3 | 7.4 | 11+ |
| 8.0 | 8.0 | 17+ |
排查方式其实很简单:看构建日志最前面有没有 Unsupported Gradle 或 This version of Gradle requires 的提示。另外,我习惯在 gradle-wrapper.properties 里固定版本号,避免 CI 环境和本地不一致。
# gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
./gradlew build --dry-run 确认不出错再提交。2. 依赖冲突
依赖冲突太常见了。项目大了以后,多个模块引入同一库的不同版本,Gradle 就会报 Conflict with dependency。我记得有一次线上用了 OkHttp 3.x 和 Retrofit 2.x 自带的 OkHttp 4.x 打架,直接导致运行时 NoSuchMethodError。CI 能编译通过,但一运行就崩。
排查依赖冲突,我推荐用这俩命令:
# 查看所有依赖树,找出冲突
./gradlew :app:dependencies --configuration debugRuntimeClasspath
# 针对某个具体依赖深入排查
./gradlew :app:dependencyInsight --dependency okhttp --configuration debugRuntimeClasspath
比方说,想看看 OkHttp 到底被哪些库引入了,用 dependencyInsight 后你会看到类似:
com.squareup.okhttp3:okhttp:4.9.0 (by 2 rules)
variant "compile" [
com.squareup.retrofit2:retrofit:2.9.0 (requires okhttp 4.9.0)
com.squareup.okhttp3:logging-interceptor:3.12.0 (requires okhttp 3.12.0)
]
怎么解决?我在 build.gradle 里用 force 或 exclude 统一版本:
configurations.all {
resolutionStrategy {
// 强制使用统一版本
force 'com.squareup.okhttp3:okhttp:4.9.0'
// 或者排除某个传递依赖
exclude group: 'com.squareup.okhttp3', module: 'logging-interceptor'
}
}
force 后不测试,结果运行时还是崩了——因为 force 只保证编译期统一,运行时如果类路径里还有旧版本(比如其他 module 的 aar 里打包了),仍然会出问题。所以最好用 strictly 或直接用 BOM 统一管理。
3. JVM 内存不足
CI 机器上跑构建,最烦的就是 OutOfMemoryError 或 GC overhead limit exceeded。为啥会这样?说白了,Gradle 默认的 JVM 参数是按普通开发机器设的,CI 上如果同时跑多个任务,堆内存很容易撑爆。
常见症状:
- 构建中途卡死,最后打印
java.lang.OutOfMemoryError: Java heap space - 构建时间突然变长(因为 GC 频繁)
- Daemon 进程被系统 OOM Killer 杀掉
排查方法:
- 查看 CI 日志里有没有
Java heap space或PermGen字眼。 - 在 CI 上显式指定 Gradle JVM 参数。我在
gradle.properties里这样写:
# gradle.properties
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
如果是 Docker 容器里跑 CI,还要注意容器自身的内存限制。我遇到过 Docker 分配 2G,但 Gradle 申请 4G —— 系统直接杀进程。
org.gradle.parallel=true
org.gradle.workers.max=2
4. Daemon 崩溃
Daemon 崩溃一般发生在 CI 环境不稳定的时候。比如构建到一半,日志里突然出现 Daemon is shutting down 或者 Gradle Daemon disappeared。为啥?常见原因:
- CI 节点内存压力大,系统杀掉了 Gradle 的守护进程
- Gradle 缓存损坏
- Daemon 版本和当前 Gradle 版本不匹配(比如 CI 上同时跑不同版本的 Gradle 任务)
排查步骤:
- 先看日志末尾有没有
Process 'Gradle build daemon' finished with non-zero exit value。 - 如果看到
InvalidDaemonInstallationException,大概率是缓存问题。 - 临时解决方案:加上
--no-daemon重新跑一次,绕过 Daemon 直接构建。
./gradlew clean build --no-daemon
注意:--no-daemon 会让每次构建启动一个新 JVM,速度会慢 30% 左右,只适合临时绕过。
org.gradle.daemon.idletimeout=600000(10 分钟空闲超时),避免 Daemon 长时间占用内存。同时确保 CI 节点没有其他资源密集型任务和 Gradle 抢内存。
综合排查流程
最后,给大家画个总结。遇到 Gradle 构建失败,别慌,按这个顺序检查:
- 看日志头部:版本不兼容的信息通常出现在前 10 行。
- 看依赖树:
./gradlew dependencies跑一遍,找冲突。 - 看 JVM 参数:
gradle.properties里的内存设置。 - 看 Daemon 状态:
./gradlew --status确认守护进程是否正常。 - 加
--stacktrace: 重新跑一次,拿到完整堆栈。
嗯,这四个问题覆盖了 90% 的 Gradle 构建失败场景。剩下的 10% 可能是插件 bug、网络超时等,但只要有日志和 stacktrace,总能找到根因。记住,日志才是你最好的朋友。