编译优化:Gradle构建优化、增量编译、KSP vs KAPT、构建缓存配置
说到Android编译优化,我脑子里第一个蹦出来的词就是「等」。等编译、等打包、等部署——这些时间加起来,一天下来可能有一两个小时就这么白白浪费了。说实话,早期我做项目时,对这块并不上心,觉得「反正电脑性能好,跑就完了」。直到有一次,一个中型项目每次全量编译要6分钟,团队10个人,一天每人编译10次,你算算这得浪费多少时间?
从那以后,我开始认真研究编译优化。今天咱们就聊聊Gradle构建优化、增量编译、KSP vs KAPT,还有构建缓存配置。这些内容说白了,就是帮你把「等」的时间砍掉一半以上。
Gradle构建优化:从配置到执行
Gradle的构建过程,我习惯把它分成三个阶段:初始化、配置、执行。大部分优化都集中在配置和执行阶段。
1. 升级Gradle和AGP版本
这是最直接、最省力的优化。每次Gradle大版本更新,都会带来性能提升。我记得Gradle 7.0之后,引入了配置缓存,效果非常明显。AGP(Android Gradle Plugin)也一样,从4.2升级到7.x,构建速度能提升20%-30%。
核心建议:保持Gradle和AGP在最新稳定版。不要用beta版,除非你想当小白鼠。
2. 启用并行编译和守护进程
在gradle.properties里加上这几行:
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m
org.gradle.caching=true
并行编译能让多模块项目同时构建,守护进程则避免了每次启动JVM的开销。我见过不少项目,明明是多模块架构,却没开并行编译,白白浪费了多核CPU的性能。
3. 按需配置(On-demand Configuration)
这个功能在Gradle 4.6之后就有了。它只配置你需要的模块,而不是所有模块。对于大型项目,效果立竿见影。
org.gradle.configureondemand=true
不过要注意,有些插件可能不兼容这个模式。我遇到过一次,某个自定义插件在按需配置下行为异常,排查了半天才发现是插件内部依赖了未配置的模块。
增量编译:只改什么就编译什么
增量编译,说白了就是「只编译你改动的部分」。Kotlin编译器从1.3.30开始支持增量编译,但需要显式开启。
在gradle.properties里加上:
kotlin.incremental=true
kotlin.compiler.incremental=true
嗯,这里要注意一点:增量编译对Kotlin的支持不如Java那么成熟。Java的增量编译几乎无感,但Kotlin有时会因为注解处理器或泛型擦除等问题,触发全量编译。
我的经验:如果发现增量编译频繁失效,可以检查一下是不是有自定义注解处理器。有些APT处理器会标记所有文件为「脏」,导致每次都是全量编译。
另外,ABI(Application Binary Interface)兼容性也很关键。如果你的模块A修改了公共API,模块B依赖了A,那么B也需要重新编译。这就是为什么我建议模块化设计时,尽量保持接口稳定。
KSP vs KAPT:注解处理器的选择
这个话题最近很热。KAPT(Kotlin Annotation Processing Tool)是Kotlin官方早期的注解处理器方案,它通过生成Java桩文件来兼容Java的APT。但问题也出在这里——生成桩文件需要时间,而且会破坏增量编译。
KSP(Kotlin Symbol Processing)是后来推出的替代方案。它直接处理Kotlin源码,不需要生成桩文件,速度比KAPT快2-3倍。
| 对比项 | KAPT | KSP |
|---|---|---|
| 处理方式 | 生成Java桩文件 | 直接处理Kotlin源码 |
| 编译速度 | 较慢(桩文件生成耗时) | 快2-3倍 |
| 增量编译支持 | 较差 | 良好 |
| 兼容性 | 兼容Java APT | 需要库作者适配 |
| 典型使用场景 | Room、Dagger、Glide | Room、Glide、Moshi |
我个人建议:新项目直接上KSP。老项目迁移也不难,大部分主流库都已经支持KSP了。比如Room从2.4.0开始支持KSP,Glide从4.13.0开始支持。
避坑指南:我曾经把一个使用Dagger的项目从KAPT迁移到KSP,结果发现Dagger的KSP支持还在实验阶段,有些功能不完整。最后只能回退。所以迁移前一定要确认库的KSP支持状态。
构建缓存配置:让重复劳动归零
构建缓存是Gradle提供的一个强大功能。它会把构建产物缓存起来,下次构建时如果输入没有变化,就直接用缓存,跳过编译。
配置方式很简单:
// settings.gradle.kts
buildCache {
local {
isEnabled = true
directory = File(rootDir, "build-cache")
removeUnusedEntriesAfterDays = 30
}
remote(HttpBuildCache::class.java) {
isEnabled = true
url = uri("https://your-cache-server.com/cache/")
isPush = true
credentials {
username = "cache-user"
password = "cache-pass"
}
}
}
本地缓存适合单机开发,远程缓存适合团队协作。我见过一个团队,配置了远程缓存后,CI上的构建时间从15分钟降到了3分钟——因为大部分依赖库的编译结果都被缓存了。
不过要注意,缓存命中率是关键。如果每次构建都因为某些原因导致缓存失效,那配置了也白搭。常见的缓存失效原因包括:
- 时间戳变化(比如每次构建都生成新的BuildConfig)
- 非确定性任务(比如某些插件每次生成不同的输出)
- 依赖版本频繁变动
我习惯在CI上开启--build-cache参数,并配合--scan查看缓存命中情况。如果命中率低于80%,就该排查问题了。
知识体系总览
下面这张图,是我对编译优化核心逻辑的总结。你可以把它当作一个检查清单,看看自己的项目在哪些环节还有优化空间。
编译优化这件事,说白了就是「减少重复劳动」。Gradle构建优化解决的是配置和执行效率问题,增量编译解决的是「只改什么就编译什么」,KSP替代KAPT解决的是注解处理的速度问题,构建缓存解决的是「同样的东西不编译第二次」。
我建议你从最基础的开始:先升级Gradle和AGP版本,开启并行编译和守护进程。然后检查增量编译是否生效,再考虑迁移到KSP。最后,如果团队协作频繁,配置远程缓存会带来意想不到的收益。
嗯,优化不是一蹴而就的。每次改动后,用--scan看看构建报告,对比一下时间变化。你会发现,这些看似微小的调整,累积起来就是每天多出来的半小时、一小时。