14、性能优化:配置缓存、构建缓存、并行执行、守护进程调优、避免反模式
说实话,Gradle 构建慢起来,真的让人抓狂。
我记得刚接手一个老项目时,一次干净的构建要跑 8 分钟。每次改一行代码,等编译的时间都够泡杯咖啡了。后来我花了一周时间专门优化构建性能,愣是把时间压到了 2 分钟以内。今天我就把这些压箱底的经验分享给你。
14.1 配置缓存:让配置阶段飞起来
Gradle 的构建分三个阶段:初始化、配置、执行。配置阶段会执行所有 build.gradle 里的代码,哪怕你只改了一个文件。你想想看,这得多浪费?
配置缓存(Configuration Cache)就是来解决这个问题的。它会把配置阶段的计算结果缓存下来,下次构建时直接复用。
核心原理:配置缓存将 build.gradle 的执行结果序列化到磁盘,后续构建跳过配置阶段,直接加载缓存。
启用方式很简单,在 gradle.properties 里加一行:
org.gradle.configuration-cache=true
但要注意,不是所有脚本都兼容。我在项目中遇到过自定义 Task 里直接访问文件系统导致缓存失效的情况。解决办法是把这些操作放到 doLast 或 doFirst 里。
警告:启用配置缓存后,如果构建脚本里有动态创建 Task 的逻辑,可能会报错。建议先在 CI 上跑一轮测试。
14.2 构建缓存:避免重复劳动
构建缓存(Build Cache)和配置缓存是两码事。它缓存的是 Task 的输出结果。比如你改了 A 模块的代码,B 模块没动,那 B 的编译结果就可以从缓存里拿。
我个人习惯在 gradle.properties 里这样配置:
org.gradle.caching=true
org.gradle.caching.debug=false
远程缓存更适合团队协作。我在公司搭了个本地缓存服务器,配置如下:
// settings.gradle
buildCache {
local {
enabled = true
directory = "${rootDir}/.build-cache"
removeUnusedEntriesAfterDays = 30
}
remote(HttpBuildCache) {
url = 'https://cache.example.com/cache/'
push = true
}
}
小技巧:本地缓存目录建议放在 SSD 上,能明显提升读写速度。我曾经把缓存从 HDD 移到 SSD,构建时间直接缩短了 15%。
14.3 并行执行:让 CPU 满负荷运转
现在的电脑动不动就 8 核 16 线程,但 Gradle 默认只用一个线程。这不是暴殄天物吗?
启用并行执行很简单:
org.gradle.parallel=true
还可以指定线程数:
org.gradle.workers.max=4
不过要注意,并行度不是越高越好。我试过把 workers 设成 16,结果 CPU 跑满了,但磁盘 I/O 成了瓶颈,反而更慢。建议根据项目大小和机器配置来调,一般设成 CPU 核心数的一半到三分之二比较合适。
| CPU 核心数 | 推荐 workers | 适用场景 |
|---|---|---|
| 4 | 2 | 小型项目 |
| 8 | 4 | 中型项目 |
| 16 | 8-10 | 大型项目 |
14.4 守护进程调优:别让它成为累赘
Gradle 守护进程(Daemon)是个好东西。它会在后台保持一个 JVM 进程,避免每次构建都重新加载。但默认配置不一定适合你的项目。
我建议在 gradle.properties 里调整 JVM 参数:
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
这里有几个要点:
- 堆内存:一般 2GB 起步,大型项目可以给到 4GB
- 元空间:512MB 通常够用,如果遇到 OutOfMemoryError 再调大
- 堆转储:加上这个参数,万一 OOM 了还能分析原因
注意:守护进程的 JVM 参数不要设得太大,否则会挤占系统资源。我曾经把 Xmx 设成 8GB,结果 IDE 都卡死了。
查看守护进程状态:
./gradlew --status
如果发现守护进程占用内存异常,可以手动停掉:
./gradlew --stop
14.5 避免反模式:这些坑我替你踩过了
优化构建性能,除了做加法,还要做减法。有些写法看着没问题,实际上拖慢了整个构建。
反模式一:在配置阶段执行耗时操作
我曾经见过有人在 build.gradle 里直接执行网络请求:
// ❌ 错误示范
def version = new URL("http://example.com/version.txt").text
这会导致每次配置阶段都去请求网络。正确的做法是放到 Task 里:
// ✅ 正确做法
task fetchVersion {
doLast {
def version = new URL("http://example.com/version.txt").text
// ...
}
}
反模式二:滥用 allprojects 和 subprojects
很多人喜欢在根项目的 build.gradle 里这样写:
// ❌ 错误示范
subprojects {
android {
compileSdk 34
}
}
这会让每个子模块都应用这个配置,哪怕它根本不需要。更好的做法是只在需要的模块里单独配置。
反模式三:动态依赖版本
// ❌ 错误示范
implementation 'com.example:library:+'
使用 + 会让 Gradle 每次构建都去检查最新版本,导致缓存失效。我建议锁定版本号:
// ✅ 正确做法
implementation 'com.example:library:1.2.3'
反模式四:不必要的文件监听
在开发时,Gradle 会监听文件变化来触发增量构建。但如果监听了太多文件,反而会拖慢速度。可以在 gradle.properties 里限制:
org.gradle.vfs.watch=false
嗯,这个选项在 CI 环境里一定要关掉,否则会白白浪费资源。
14.6 知识体系总览
下面这张图总结了本章的核心内容,你可以把它当作优化清单来用:
说实话,性能优化是个持续的过程。不要指望一次配置就能一劳永逸。我建议你每次改完配置后,用 ./gradlew --profile 生成一份报告,看看瓶颈到底在哪。
我的经验:优化构建性能,80% 的收益来自 20% 的改动。先搞定配置缓存和构建缓存,这两个效果最明显。其他的可以慢慢调。
好了,这一章的内容就到这里。记住,构建速度就是开发效率。花点时间优化,绝对值得。