构建速度瓶颈诊断方法论:从宏观到微观的排查思路

做构建优化,最怕什么?最怕上来就动手,改了一堆配置,结果速度没变,反而编译崩了。我见过不少团队,一升级 AGP 就跑来问我:“快帮我看看,怎么构建变慢了?”我一问“你有基线数据吗?”,对面沉默了。

嗯,这就是今天要聊的核心——怎么科学地诊断构建瓶颈。不靠直觉,不靠猜,靠数据和分层分析。

为什么要量化构建速度?

量化是优化的前提。没有数据支撑,你根本不知道瓶颈在哪儿,也不知道优化完到底有没有效果。

  • 基线数据:AGP 升级前,先记录一次完整的构建耗时(clean build + incremental build)。
  • 增量数据:升级后再跑一次,对比差异。
  • 关键指标:总耗时、配置阶段耗时、task 执行耗时、GC 暂停时间、I/O 等待时间。

我个人习惯用这个命令来采集基线:

./gradlew assembleDebug --profile --build-cache --parallel \
    -Pandroid.enableBuildCache=true \
    -Pandroid.useAndroidX=true \
    2>&1 | tee build.log

产出物:Build Profile HTML 报告 + build.log 日志。这就是你优化路上的“体检报告”。

量化不是一次性的。每改动一个优化点,都要重新跑一次,收集新数据,才能判断改进方向对不对。

宏观层:先看整体耗时与资源监控

别一上来就扎进瀑布图里找细节。我建议先看三个宏观指标:

  1. 总构建耗时 —— 直接告诉你是快是慢。
  2. CPU 使用率 —— 如果构建过程中 CPU 空闲很多,说明瓶颈在 I/O 或锁等待。
  3. 内存与 GC —— 频繁 GC 会导致构建线程“假死”。

拿 Mac 上的 Activity Monitor 或者 Linux 上的 top / htop 看一眼就心里有数。举个例子,我在一个项目中看到构建期间 CPU 利用率只有 40%,但 I/O 读写在 100% 打满。明显是慢在磁盘读写上,后来换成 SSD 就够了。

一个小技巧:运行构建前执行 export GRADLE_OPTS="-Xmx4g -XX:+PrintGCDetails",可以观察 GC 日志。如果看到持续 Full GC,赶紧去调堆内存。

微观层:瀑布图里挖真凶

宏观排查完,基本知道是 CPU 问题、I/O 问题还是内存问题。接下来就要打开 Build Profile 瀑布图,看看具体哪个任务在“拖后腿”。

瀑布图怎么看?我把它拆成三块:

区域 关注点 常见问题
配置阶段 (Configuration) 耗时是否超过 10 秒 settings.gradle 里动态计算多,plugin 加载慢
任务执行阶段 (Task Execution) 每个 Task 的 Duration、Start Time transformClassDex 慢、mergeNativeLibs 慢
依赖解析阶段 (Dependency Resolution) 远程仓库访问次数、耗时 没有使用 local maven cache,每次都去远端拉

我一般按这个顺序排查:

  1. 找到耗时最长的 Task(通常是 transformClassesWithDexBuilderFor... 或 mergeExtDexFor...)。
  2. 点开看它的子任务,看是 I/O 密集还是 CPU 密集。
  3. 对比不同 Task 的 并行度。如果很多 Task 在同一个时间点串行执行,说明依赖锁导致没有充分利用并行。

我曾经踩过一个坑:升级 AGP 8.0 后,mergeNativeLibs 耗时从 3 秒暴涨到 35 秒。瀑布图里看不出原因,后来开 enableSdkSupportAPK, 才发现是 AGP 新增了对 native code 的 strip 步骤。这种隐性增量,光靠看瀑布图是看不出来的,必须结合 --info 日志。

设定优化目标:SMART 原则的落地

优化目标不能是“让构建更快”这种空话。我用一个表格帮你把目标写清楚:

维度 当前值 目标值 衡量方式
Clean Build 耗时 180 秒 ≤ 120 秒 ./gradlew clean assembleDebug --profile
Incremental Build 耗时(修改一个 Java 文件) 45 秒 ≤ 15 秒 同上,只改一个文件
配置阶段耗时 8 秒 ≤ 3 秒 ./gradlew assembleDebug --profile 的 Configuration 时间
GC 暂停总时间 12 秒 ≤ 5 秒 Gradle 日志中的 GC 信息

目标定好后,每做完一个优化,就对比一次。不要一次改太多,否则你根本不知道是哪一步起了作用。

避免盲目优化的三个原则

说白了,优化是“有限资源下的取舍”。我总结了三条铁律:

  • 先测量,后动手。没有基线,一切优化都是瞎搞。
  • 单变量原则。一次只改一个配置,观察效果,再改下一个。
  • 关注全流程而不只是单点。你优化了一个 Task 10 秒钟,但如果另一个 Task 变成了瓶颈,整体收益可能被吃掉。

举个例子,有人把 maxWorkers=8 改成 16,结果整体构建反而变慢了。为什么?因为加多线程后,锁竞争加剧,GC 更频繁了。这就是典型的盲目优化——没有数据支撑,以为调大 worker 数就能加速,结果适得其反。

构建速度瓶颈诊断:从宏观到微观排查流程 宏观层:整体耗时与资源监控 1. 总构建耗时(基线) 2. CPU/内存/IO 利用率 3. GC 暂停时间 是否有明显资源瓶颈? 是 → 先行优化资源 否 → 进入微观层 微观层:瀑布图任务诊断 配置阶段耗时(>10s) settings.gradle 与 plugin Task 执行瓶颈(最长) dex/merge/lint 等 依赖解析慢 远程仓库/未缓存 量化目标:定义当前值 → 目标值 → 衡量方式 单变量优化 → 重新测量 核心铁律 先测量后动手 单变量原则 关注全流程

最后,送大家一句话:优化不是玄学,是科学。你只要按这个“宏观 → 微观 → 量化目标 → 单变量验证”的流程走一遍,不出三次迭代,就一定能看到效果。我在好几个大型项目里就是这么干的,百试百灵。