构建速度问题概述
说实话,AGP 升级这件事,我一开始是挺兴奋的。新特性、新优化,想着肯定能让构建更快。结果呢?项目一跑,直接傻眼——构建时间反而暴涨了 30% 以上。当时组里的同学都看我,我脸上挂不住啊。
后来我花了整整两周,翻了 Gradle 源码、看了 AGP 的 release notes,才慢慢搞清楚问题出在哪。今天这部分,我就把踩过的坑和总结的经验,一五一十讲给你听。
AGP 升级带来的"隐形"负担
很多人以为 AGP 升级就是换几个版本号,点个 Sync 就完事了。其实不然。我自己的项目从 AGP 4.2 升到 7.4,编译时间从 2 分半变成了 4 分钟。为什么?我列几个常见的"隐形杀手":
- 增量构建失效:新版本改了任务依赖关系,以前能走增量的任务现在得全量跑。我遇到过资源压缩任务每次都要重跑所有图片——白白浪费 30 秒。
- 新的编译优化带来的副作用:比如 R8 的优化更激进,但处理时间也更长。你想想看,原来 ProGuard 只处理 30 秒,现在 R8 要 1 分半。
- 资源打包逻辑变化:AAPT2 升级后,资源 ID 分配不能完全复用,每次构建都得重新解析一遍资源表。这对大项目尤其致命。
- 插件兼容性问题:一些第三方插件还没适配新 AGP,会强制走降级路径,甚至触发重复执行。
⚠️ 避坑指南:我曾经在升到 AGP 7.0 后,发现每次改一行代码都要等 2 分钟。最后定位到是 databinding 插件在新版里触发了全量资源处理。解决办法是升级到 7.1+ 并开启 android.databinding.enableV2。
怎样才算"慢"?——量化指标
没有数据支撑的优化都是耍流氓。我习惯先给团队定个基线:clean build 不能超过 5 分钟,incremental build 不能超过 30 秒。超过这个数,就得查问题了。
下面是我整理的一个参考表格(基于 500+ module 的中等项目):
| 阶段 | AGP 4.2 平均耗时 | AGP 7.4 平均耗时 | 升幅 |
|---|---|---|---|
| 增量编译(改 1 个 Java 文件) | 12s | 22s | +83% |
| 全量编译(clean build) | 2m30s | 4m10s | +67% |
| Dex 处理(R8) | 35s | 58s | +66% |
| 资源打包 | 18s | 32s | +78% |
看到这数据,你肯定想问:那到底怎么定位瓶颈?用 Gradle Profile?我试过,太粗。它只告诉你哪个 task 慢,但是为什么慢、跟其他 task 有什么依赖关系,完全看不出来。这时候,我就想到了网络瀑布图。
网络瀑布图——一种新的分析视角
说白了,网络瀑布图原本是前端用来分析页面加载性能的——每个请求什么时候开始、什么时候结束、并行度怎么样,一目了然。但你想啊,构建过程也是一堆 task 在按依赖关系"请求"资源,本质上跟页面加载没什么区别。
我花了点时间,把 Gradle 的 task 执行日志转成瀑布图格式,直接在浏览器里看。效果惊艳——一眼就能看出哪些 task 在排队等锁,哪些 task 可以并行但没并行。
举几个实际用瀑布图发现的问题:
- 两个完全独立的任务(比如
mergeRes和compileJava)被同一个线程池限制,导致串行执行。 - 某个插件在
preBuild阶段加了 3 秒的同步锁,后续所有任务都等着。 - 依赖里有个小版本冲突,导致每次构建都要重新解析整个依赖树。
💡 个人习惯:我一般先看瀑布图里有没有"断层"——就是一段很长的时间线里只有一个 task 在跑,其他都是空白。那基本上就是并行度没打满,或者有隐式依赖。
我常用的分析流程
这套流程我在三个项目里验证过,都成功把构建时间降回了升级前的水平。你可以直接拿来用:
- 对比基线:先跑一次 clean build + 一次 incremental build,记录总时间和各阶段耗时。
- 导出构建扫描:用
--scan参数,或者直接采集 Gradle 输出的 JSON 日志。 - 生成瀑布图:我写了个小脚本(Python),解析 JSON 日志里的 task start/stop 时间,丢进一个 HTML 模板里渲染。
- 找"胖"任务和"闲"时间:哪个 task 占的时间最长?哪段时间所有 task 都在等?一一标记。
- 交叉验证:用 Android Studio 的 Profile 工具跟瀑布图对照,确保不是采集误差。
- 逐个击破:针对找到的瓶颈,调整并行度、优化依赖、升级插件版本。
记住:不要凭感觉优化。我见过有人一上来就调 org.gradle.parallel 和 max-workers,但问题其实是某个任务占用了大量 I/O 导致磁盘瓶颈。瀑布图可以帮你区分到底是 CPU 瓶颈还是 IO 瓶颈。
好了,理论说完,下面这张图是我自己画的分析框架,你看一眼就能明白整体思路:
嗯,这张图把整个排查思路串起来了。核心就是:不要只盯着 profile 看数字,要用可视化时间线把依赖关系暴露出来。