Gradle构建卡在磁盘I/O时,首要确认是否真为磁盘瓶颈:Linux/macOS用iostat -x 1看%util>90%或await>20ms,Windows查资源监视器“响应时间”和“活跃时间”;再结合--no-daemon耗时对比、profile报告中extract/copy类任务耗时、以及--dry-run判断增量失效,精准定位重复读写根源。

Gradle 构建卡在磁盘 I/O(比如反复读写 ~/.gradle/caches/、build/ 目录,或频繁解压 AAR/JAR)时,往往不是“硬盘太慢”,而是缓存策略、文件系统行为或任务设计不合理导致的。优先排查是否真被磁盘拖慢,再针对性干预。
怎么确认磁盘读写是瓶颈
别猜,用工具看实际 I/O 压力:
- Linux/macOS 下跑构建时另开终端执行
iostat -x 1,观察%util(设备利用率)和await(I/O 平均等待时间),持续 >90% 或await > 20ms才算真瓶颈 - Windows 可用资源监视器 → 磁盘活动 → 查看“响应时间”和“活跃时间”
- 对比
time ./gradlew build -x test和time ./gradlew build -x test --no-daemon:如果后者耗时暴涨,说明磁盘压力主要来自重复加载缓存或初始化,而非构建输出本身 - 检查
build/reports/profile/报告里 “Task Execution” 阶段中耗时最长的几个任务是否集中在extract*、copy*、processResources这类 I/O 密集型任务上
gradle.properties 缓存配置对磁盘影响最大
默认配置下 Gradle 会反复校验、解压、复制依赖包,这些全是小文件随机读写。关键开关必须明确设值:
-
org.gradle.caching=true:启用构建输出缓存,避免重复编译、资源处理;但注意它默认只缓存任务输出,不缓存依赖下载——依赖下载仍是磁盘热点 -
org.gradle.configuration-cache=true:减少每次构建时重复解析build.gradle脚本产生的临时文件读写,对多模块项目效果明显 -
org.gradle.parallel=true:并行任务会增加并发磁盘请求,但如果 SSD 性能尚可,反而能摊薄单任务等待时间;HDD 上慎用,可能加剧寻道抖动 - 禁用无意义的缓存写入:
org.gradle.configuration-cache.problems=warn避免因配置缓存失败触发大量 fallback 日志写入
依赖解压和 AAR 处理是隐藏 I/O 洪水
Android 项目尤其明显:每个 AAR 包都会被 Gradle 解压到 build/intermediates/exploded-aar/(旧 AGP)或 build/intermediates/aar_compile_dependencies/(新 AGP),一个中型项目轻松产生数万小文件。这不是缓存问题,是设计使然。
- AGP 8.0+ 默认启用
android.useNewResourceProcessing=true,它用更紧凑的二进制格式替代传统解压流程,显著减少磁盘写入;确认该值未被覆盖为false - 避免在
build.gradle中手动调用zipTree或tarTree处理依赖包——这类操作无法被增量编译识别,每次构建都重来 - 把
~/.gradle/caches/移到 SSD 是最简单见效的手段;如果团队共用 NFS 或机械盘,I/O 竞争会直接放大问题 - CI 环境中,不要用
rm -rf build/清目录后立刻构建:Gradle 会重建所有中间文件。改用./gradlew cleanBuildCache+--refresh-dependencies组合更精准
自定义任务破坏增量编译是静默杀手
很多团队写的拷贝、代码生成、资源注入类任务,表面看不耗 CPU,实则每构建一次就全量重写输出目录,强迫后续任务重新读取——这才是磁盘压力的真正源头。
- 检查所有自定义
Copy、Sync、Exec任务是否声明了inputs和outputs;没声明 = 每次都执行 - 用
FileCollection替代File数组作为输入,否则 Gradle 无法做增量判断 - 避免在任务 action 里直接操作
project.buildDir下的任意路径;应严格限定在outputs.files定义的路径内 - 运行
./gradlew build --dry-run,观察哪些任务显示UP-TO-DATE—— 如果本该跳过的任务每次都执行,就是增量逻辑失效了
磁盘 I/O 瓶颈最难调试,因为它常混在“构建慢”的表象里,被误判为网络或 CPU 问题。真正要盯住的不是硬盘型号,而是 Gradle 是否在反复做同一件事:解压同一个 AAR、重写同一份资源、校验同一组 JAR 的 SHA256。这些动作一旦失控,再快的 NVMe 也扛不住每秒几百次的小文件操作。











