Android Gradle 构建性能优化
Android Gradle 构建性能优化
项目变大后,构建慢会直接影响开发反馈速度。构建耗时可能来自配置阶段脚本执行、依赖解析、注解处理、资源合并和无法增量的自定义任务。优化不能只增加机器资源,应先用构建分析找到最耗时且最常发生的路径。
一、区分配置与执行阶段
Gradle 首先读取项目和模块配置,再执行任务。模块数量增加后,即使只编译一个小改动,配置阶段也可能消耗明显时间。
构建脚本中应避免在配置阶段扫描大目录、读取大量文件或执行外部命令。只有任务真正运行时才需要的值,应延迟到任务执行阶段获取。
二、测量真实开发场景
至少分别记录:
- 完全清理后的构建。
- 修改 Kotlin 或 Java 文件后的增量构建。
- 修改资源后的构建。
- 仅运行单元测试。
- 切换分支后的首次构建。
开发者最频繁的是增量构建,不能只优化偶尔发生的完整构建。
三、保持任务可增量与可缓存
自定义任务需要声明准确输入和输出。输入声明过宽会导致任何文件变化都重新执行,缺少输出声明则无法复用结果。
abstract class GenerateFeatureListTask : DefaultTask() {
@get:InputFiles
abstract val sourceFiles: ConfigurableFileCollection
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun generate() {
val features = sourceFiles.files
.sortedBy(File::getName)
.map(File::getNameWithoutExtension)
outputFile.get().asFile.writeText(features.joinToString("\n"))
}
}
输出必须只依赖声明输入,不能隐式读取当前时间或随机值,否则缓存结果不可靠。
四、减少依赖暴露
模块内部实现依赖应保持内部可见,避免传递给所有下游。公共依赖变化会让更多模块重新编译。
模块边界清晰后,小范围代码改动只影响相关模块。但过度拆分也会增加配置成本,应以编译影响和团队边界为依据。
五、优化代码生成
注解处理器和代码生成器可能成为编译热点。优先使用项目已验证的增量方案,删除不再使用的处理器,并检查是否对所有模块无条件启用。
生成代码输入应稳定且范围精确。一个业务模型变化不应迫使无关模块重新生成全部文件。
六、控制资源规模
大量重复图片、多语言资源和无用资源会增加合并与处理成本。公共资源集中复用,业务资源遵循模块前缀,避免冲突和重复。
资源清理必须确认引用方式,动态名称查找可能无法被静态分析发现。不要为了减小构建时间直接删除未确认资源。
七、统一构建约定
每个模块复制相同插件和编译配置,升级时容易不一致。项目可以通过现有约定插件统一公共设置,模块脚本只保留自身差异。
约定层不应隐藏所有行为。关键编译版本、变体和开关仍要有清晰来源,方便定位某个模块为何执行不同任务。
八、避免动态和重复解析
依赖版本应明确稳定,避免每次构建都需要确认变化。仓库和依赖声明集中管理,可以减少重复配置和版本冲突。
离线开发只在本地缓存完整且业务允许时使用,不能掩盖缺失依赖。持续集成环境需要验证从干净缓存仍能可靠构建。
九、合理使用并行与内存
并行执行能提升相互独立任务的吞吐,但过多任务会争抢处理器、内存和磁盘。守护进程内存过小可能频繁回收,过大又会挤压开发工具。
参数应根据项目规模和团队机器测量,不直接复制其他项目配置。出现构建进程退出时,先确认内存曲线和具体任务。
十、防止性能回归
可以定期记录关键构建路径、最慢任务和缓存命中率。引入新插件或生成器时,对比前后增量构建,而不是只验证功能可用。
优化后还要确认:
- 输出产物一致。
- 增量构建不会漏编译。
- 干净环境能够成功构建。
- 不同变体和最低兼容配置仍正确。
- 构建脚本保持团队可理解性。
总结
Gradle 构建优化从测量开始。减少配置阶段工作,为自定义任务声明精确输入输出,控制依赖暴露和代码生成范围,再根据真实设备调整并行与内存。持续关注高频增量构建,才能稳定缩短开发反馈周期,而不牺牲构建正确性。
- 点赞
- 收藏
- 关注作者
评论(0)