构建系统调试:调试编译过程、分析依赖关系、优化构建流程
说实话,很多Android开发者对构建系统的态度就是「能跑就行」。
但我在大厂带团队时发现,真正拉开项目效率差距的,往往就是构建系统调优这步。你想想看,一个大型项目编译一次半小时,一天编译十次就是五个小时——这时间够你刷完一部电影了。
所以今天咱们聊聊怎么调试编译过程、分析依赖关系、优化构建流程。嗯,这些都是我踩过坑之后才真正搞明白的东西。
调试编译过程:从黑盒到白盒
构建系统调试,说白了就是搞清楚「编译到底在干什么」。
我个人习惯先打开编译日志。Gradle提供了几个很有用的参数:
# 查看完整的编译日志
./gradlew assembleDebug --info
# 查看更详细的依赖分析
./gradlew assembleDebug --debug
# 只看错误和警告
./gradlew assembleDebug --warn
我曾经遇到过一个诡异的问题:每次增量编译都比全量编译还慢。打开--info日志一看,发现某个模块的task永远不命中缓存。原因?那个模块的输入文件里包含了一个自动生成的时间戳文件。
核心思路:调试编译过程的关键是找到「不该重做却重做了」的task。用--info参数看UP-TO-DATE状态,用--debug看具体跳过原因。
分析依赖关系:别让依赖变成一团乱麻
依赖关系分析,是构建优化的基础。我见过最夸张的项目,依赖图长得像蜘蛛网——一个模块依赖了十几个其他模块,改一行代码触发整个项目重编。
Gradle提供了依赖分析工具:
# 查看项目的依赖树
./gradlew :app:dependencies
# 查看特定配置的依赖
./gradlew :app:dependencies --configuration implementation
# 输出依赖图到文件
./gradlew :app:dependencies > deps.txt
输出结果长这样:
+--- androidx.appcompat:appcompat:1.6.1
| +--- androidx.activity:activity:1.6.0
| | +--- androidx.core:core:1.9.0
| | +--- androidx.lifecycle:lifecycle-runtime:2.5.1
| | +--- androidx.savedstate:savedstate:1.2.0
| +--- androidx.fragment:fragment:1.5.5
| +--- androidx.core:core:1.9.0 (*)
| +--- androidx.cursoradapter:cursoradapter:1.0.0
| +--- androidx.customview:customview:1.1.0
看到那个(*)了吗?表示这个依赖在其他地方已经展开过了。如果同一个依赖出现多次不同版本,那就是冲突了。
我的经验:依赖分析要关注三个点——版本冲突、传递依赖过多、循环依赖。我曾经用./gradlew :app:lint找到过一个循环依赖,A依赖B,B依赖C,C又依赖A。这种问题在大型项目里特别隐蔽。
优化构建流程:从30分钟到5分钟
优化构建流程,我总结了四个字:减、并、缓、并。
| 优化策略 | 具体做法 | 效果 |
|---|---|---|
| 减(减少不必要的task) | 关闭lint检查、关闭测试、关闭不必要的product flavor | 减少20%-30%的编译时间 |
| 并(合并小模块) | 把频繁改动的模块合并,减少跨模块编译 | 减少增量编译时间50%以上 |
| 缓(利用缓存) | 开启Gradle构建缓存、使用远程缓存 | 全量编译时间减少40% |
| 并(并行编译) | 调整并行线程数、开启配置缓存 | 编译速度提升2-3倍 |
具体配置示例:
// gradle.properties
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
// 开启远程构建缓存
org.gradle.caching.http.url=https://your-cache-server.com/cache/
注意:并行编译不是线程越多越好。我试过把parallel线程数调到16,结果CPU跑满,反而更慢了。建议根据CPU核心数设置,一般4-8个线程比较合适。
构建流程可视化分析
下面这张图是我自己总结的构建优化决策流程,帮你快速定位问题:
实战案例:一次增量编译优化
我记得有一次,项目组反馈增量编译越来越慢。我打开--info日志一看,发现有个叫:lib:compileDebugKotlin的task每次都执行,明明代码没改。
排查后发现,这个模块的build.gradle里写了个自定义task,每次编译都会生成一个version.properties文件。这个文件被声明为compileKotlin的输入,所以每次都会触发重新编译。
解决方案很简单:
// 错误做法:每次生成新文件
task generateVersion {
doLast {
file("version.properties").text = "version=${System.currentTimeMillis()}"
}
}
// 正确做法:只在版本变化时生成
task generateVersion {
inputs.property("version", project.version)
outputs.file("version.properties")
doLast {
file("version.properties").text = "version=${project.version}"
}
}
改完之后,增量编译从3分钟降到了20秒。嗯,有时候问题就这么简单。
避坑指南:我曾经因为一个build.gradle里写了println("Building..."),导致每次编译都重新执行整个task。记住,println在配置阶段执行,会影响缓存命中。
总结一下
构建系统调试,说白了就是三件事:
- 看日志——用--info和--debug找到问题点
- 理依赖——用dependencies命令看清依赖关系
- 做优化——减、并、缓、并四字诀
你想想看,如果每次编译都能快5分钟,一天省下来的时间够你学多少新东西?
公众号:蓝海资料掘金营,微信deep3321