如何分析 Gradle 构建耗时并定位慢任务?
解读
在国内 Android 工程里,Gradle 构建动辄 5~10 min,CI 排队半小时是常态。面试官问“怎么找慢任务”,不是让你背几条命令,而是考察:
- 能否把“体感慢”量化成可追踪的指标;
- 能否把指标拆到“哪个阶段-哪个任务-哪段代码”;
- 能否给出落地套路(脚本、CI 缓存、远程仓库、KPI 对齐)。
一句话:既要能“看病”,也要能“开方”。
知识点
- Gradle 生命周期:Initialization → Configuration → Execution;国内常见“Configuration 过慢”是 KTS + 多模块 + apply from 乱用导致。
- Build Scan、Gradle Profiler、Gradle Doctor、Task 级 --profile 报告;国内网络下必须配置代理仓库(阿里云、腾讯、华为)否则下载依赖占大头。
- Task 输入输出快照(TaskInputs/Outputs)、增量编译(IncrementalTask)、构建缓存(BuildCache)、并行执行(--parallel)、守护进程(Daemon)、配置缓存(Configuration Cache)。
- 国内特色卡点:AAR 多维度 flavor、字节码插桩(APM、埋点、热修)、Kotlin KAPT 注解(Room、ARouter、Hilt)、Transform API 与 AGP 7.x 的 DexBuilder 合并、R 文件生成、资源合并(ResourceMerger)耗时。
- 定位套路:先整体(Scan)→ 再阶段(profile)→ 再任务(task --info)→ 再代码(自定义 Plugin 插桩)→ 最后治理(缓存、增量、降级、拆模块)。
答案
我把它拆成“三步七招”,线上与 CI 通用,全部在现网验证过。
第一步:量化
- 本地开发
打开 gradle.properties:
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
执行:
./gradlew assembleDebug --profile --scan --info
构建结束后生成 build/reports/profile/timestamp.html,同时控制台给出 Build Scan 链接(国内需配置代理仓库,否则上传失败)。 - CI 量化
在 GitLab-CI 或 Jenkins Pipeline 里加:
./gradlew assembleDebug --build-cache --scan -Dorg.gradle.workers.max=8
把 scan 结果 url 写到 PR 评论,方便回溯。
第二步:定位
3. 看阶段耗时
打开 profile.html,先对比 Configuration、Dependency Resolution、Task Execution 三栏。国内项目最常见“Configuration 30 s+”,90% 是 KTS 里 allprojects 滥用、apply from 远端脚本、repository 顺序不合理。
4. 看任务耗时
在 Task Execution 页按“Own Duration”倒序,锁定前 10 个任务。
常见慢任务黑名单:
- kaptDebugKotlin:KAPT 生成代码,注解处理器太多。
- mergeDebugResources:资源过多,且未启用 resourcePrefix。
- transformClassesWithXXXForDebug:字节码插桩插件(SensorsAnalytics、Bugly、热修)。
- dexBuilderDebug:AGP 7.x 把多 DEX 合并放这里,模块太多、依赖爆炸。
- 看重复任务
用 Gradle Doctor 插件:
plugins { id "com.osacky.doctor" version "0.8.1" }
运行后控制台会打印“Tasks that should be UP-TO-DATE but took >1 s”,直接告诉你哪些任务本可以跳过却被强制重新跑。 - 看缓存命中率
在 Build Scan → Performance → Build Cache 里,若“Cache misses”>20%,重点检查:- 任务声明了 @TaskAction 但每轮输入快照不同(例如用了 new Date()、System.currentTimeMillis());
- 任务输出路径带版本号,导致 key 永远对不上;
- 依赖仓库 SNAPSHOT 频繁刷新。
第三步:治理 7. 对症下药
- KAPT 慢:升级 Kotlin 1.8+,启用 kapt.use.worker.api=true、kapt.incremental.apt=true;把 ARouter、EventBus 换成 KSP。
- 资源合并慢:开启 android.enableResourceOptimizations=true,给每个模块加 resourcePrefix,禁用 pngcrunch(android.enablePngCrunching=false)。
- Transform 插桩慢:把 SensorsAnalytics 的 transform 改成 ASM API 增量模式,或把插桩下沉到 CI 的 Release 包,Debug 包直接关闭。
- 模块太多:用 gradle-include-guard 脚本,按需 apply 子工程;把 60 个模块拆成 20 个 aar-publish,二进制依赖替代源码依赖。
- 远端脚本慢:把原来 apply from: 'https://xxx.gradle' 下载到本地 includeBuild,CI 缓存目录挂载到 ~/.gradle/init.d。
- CI 缓存:在 Jenkins 里开“Pipeline Maven/Gradle 缓存插件”,把
/.gradle/caches、/.gradle/wrapper 做 Volume 缓存;GitLab-CI 用 cache:key: files: ['gradle/wrapper/gradle-wrapper.properties']。
落地效果:我们 220 万行代码、78 模块的工程,本地 clean build 从 11 min 降到 3 min 20 s,CI 平均缩短 6 min,每周节省 200+ 核时,KPI 直接对齐“构建效率提升 60%”。
拓展思考
- 配置缓存(Configuration Cache)与 Kotlin DSL 的兼容坑:国内很多老插件用 Project.afterEvaluate 访问 Task,导致“CC”无法启用,需要插件作者改造成 Task 注册时惰性配置。
- 远程构建缓存:Google 推荐用 Gradle Enterprise,但国内机房访问延迟高,可自建 MinIO + nginx 做 S3 兼容缓存,成本仅为 Enterprise 的 1/10。
- 版本升级 ROI:AGP 8.0 要求 JDK 17,CI 镜像要整体换,如果团队还在 JDK 8,需要评估“升级带来 30% 构建提速”与“全员换 IDEA、换镜像”之间的人力成本。
- 未来趋势:Gradle 正在推“Isolated Projects”,把每个 project 做成沙箱,彻底解决 Configuration 阶段的全局锁,一旦稳定,国内多模块巨型 App 的 Configuration 耗时有望再降 50%。