Gradle 构建性能专题
构建优化必须先用 Build Scan, Build Analyzer, profile 或 CI 时间序列定位瓶颈. 任何 “KSP 固定快 2 倍” 或 “开启配置缓存一定更快” 的结论都需要在当前项目和处理器上测量.
一, 先建立可比基线
分别记录 clean build, 增量 build, 无改动 build, 本地开发机与 CI, 并保存配置时间, 任务执行时间, 缓存命中率和关键路径. 基线必须记录 AGP/Gradle/JDK/Kotlin/KSP/处理器版本与 daemon 参数.
避免把依赖下载, 首次 daemon/JIT, IDE 索引和远程缓存网络波动混入结论.
可复现实验记录
以下是可运行命令示例; 在项目根目录执行, 命令本身不会证明优化有效. 先固定 Wrapper, JDK, AGP, 网络缓存状态和目标任务, 再将输出与同条件的基线对比.
./gradlew :app:assembleDemoDebug --profile
./gradlew :app:assembleDemoDebug --configuration-cache --configuration-cache-problems=warn
./gradlew :app:assembleDemoDebug --scan
--profile 会生成 HTML profile 报告, 适合初步定位配置和任务耗时; Build Scan 需要组织允许, 服务端可用以及可能的条款 / 数据审查, 不能把 --scan 视为默认可上传的命令. configuration cache 的问题报告说明构建逻辑兼容性, 不等于性能收益. 记录每次运行是否为 clean, 源码改动类型, local/remote cache 状态, 总时长, 配置时长, 关键路径 task 和失败/未命中原因.
二, 常见优化方向
| 问题 | 证据 | 优先动作 |
|---|---|---|
| 配置阶段慢 | configuration timeline, 配置缓存报告 | 修复不兼容插件, 减少配置期 I/O, 评估 configuration cache |
| 任务重复执行 | task history, up-to-date 原因 | 声明精确 inputs/outputs, 避免读取不稳定环境 |
| 多模块重编译 | dependency graph, ABI 变化 | 收敛 api, 使用 implementation, 拆分稳定 API 模块 |
| 代码生成慢 | KAPT/KSP task 时间, 处理器日志 | 升级或替换处理器, 减少聚合处理, 验证增量能力 |
| CI 冷缓存 | 缓存命中率, 下载时间 | 设计安全的 local/remote build cache 与依赖缓存 |
三, KSP 与 KAPT
KSP 直接面向 Kotlin symbol, 通常能减少 KAPT 的 Java stub 开销, 但收益取决于处理器实现, 增量支持, 源码规模和 Kotlin/KSP 版本. 不能给出无条件 “快 2 倍” 承诺.
迁移检查:
- 处理器是否正式支持当前 Kotlin/KSP 组合.
- 生成代码语义是否与 KAPT 版本一致.
- 是否支持 isolating/aggregating 增量模式.
- clean 与 incremental build 分别 benchmark.
- 保留回退方案, 避免多个处理器一次迁移导致难以归因.
四, Configuration Cache 与 Build Cache
- Configuration Cache 复用配置阶段结果; 插件和脚本必须满足兼容约束.
- Build Cache 复用任务输出; 任务要正确声明输入输出, 并避免不可重现数据.
- 二者解决不同阶段, 不能混称 “Gradle 缓存”.
- 远程缓存需考虑凭据, 租户隔离, 敏感产物, 缓存投毒和网络收益.
归因案例: 配置缓存未命中
现象: 连续两次无源码变更构建, configuration cache 均未复用.证据: 使用 --configuration-cache --configuration-cache-problems=warn 后, 报告指向 convention plugin 在配置期读取 git rev-parse 的未声明外部进程输出.根因: 配置模型依赖每次不可追踪的外部状态, 不能安全序列化复用.修复: 把版本信息改为受控 CI 注入的 Gradle property, 并仅在需要该值的 task 中以声明输入消费; 同时保留本地缺失时的明确失败提示.验证: 在相同 commit/JDK/属性下运行无改动构建, 检查报告显示 configuration cache reused, 并回归产物版本字段和 CI 冷缓存时间. 不要只因一次变快就合并, 也不要为了命中缓存丢失版本可追溯性.
本篇只讨论测量, 报告解释与因果归因; 常规 DSL, 变体与 targetSdk 操作见 Gradle 与工程化, 插件, Variant API, ASM, R8/retrace 见 AGP 插件与字节码工程.
五, 工程治理
- 固定 Wrapper, JDK 和插件版本, 维护兼容矩阵.
- 使用 convention plugins 收敛重复配置, 避免
subprojects {}中不可追踪的全局副作用. - 定期检查依赖图, 动态版本, 重复资源和注解处理器.
- CI 对关键构建场景保存趋势和回归阈值, 而不是只看单次成功 / 失败.
高频面试题
Q1: 如何定位 Gradle 构建慢?
先区分配置, 任务执行, 依赖下载和 daemon/JVM, 再用 Build Scan/Analyzer 找关键路径和未命中原因, 最后做单变量 A/B 对比.
Q2: KSP 一定比 KAPT 快吗?
不一定. 它避免了 KAPT 的部分 stub 模型, 但最终取决于处理器实现和增量能力, 必须在相同版本和场景下测量.
Q3: 为什么 implementation 有助于增量编译?
因为它不把依赖暴露到消费者 compile classpath, 内部实现变化不会触发上游重编译. 完整对比见 见 32.
Q4: 如何证明某项 Gradle 优化真的有效? 固定工具链和场景, 分别比较 clean, 代表性增量, 无改动以及 CI 冷 / 热缓存构建; 用 profile, Scan 或 Build Analyzer 证实关键路径和未命中原因变化, 并回归产物与测试门禁. 不能只报一次总耗时或把下载波动算成插件收益.
易错点 / 追问
- 不把 clean build 优化等同于日常增量构建体验.
- 不为了并行而无限提高 worker/heap, 避免内存抖动和机器争用.
- 不开启缓存后只看一次结果; 先检查正确性, 命中率和可复现性.
版本与参考资料
- 最后核验: 2026-08-07
- Gradle performance
- Configuration cache
- KSP overview