编译优化: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构建优化 升级Gradle/AGP版本 开启并行编译 & 守护进程 按需配置(On-demand) 增量编译 Kotlin增量编译开启 ABI兼容性管理 避免全量编译触发 KSP vs KAPT KSP速度更快(2-3倍) KAPT兼容Java APT 新项目优先选KSP 构建缓存 本地缓存 + 远程缓存 四大优化方向:减少等待、提升效率、选择正确工具、复用结果

编译优化这件事,说白了就是「减少重复劳动」。Gradle构建优化解决的是配置和执行效率问题,增量编译解决的是「只改什么就编译什么」,KSP替代KAPT解决的是注解处理的速度问题,构建缓存解决的是「同样的东西不编译第二次」。

我建议你从最基础的开始:先升级Gradle和AGP版本,开启并行编译和守护进程。然后检查增量编译是否生效,再考虑迁移到KSP。最后,如果团队协作频繁,配置远程缓存会带来意想不到的收益。

嗯,优化不是一蹴而就的。每次改动后,用--scan看看构建报告,对比一下时间变化。你会发现,这些看似微小的调整,累积起来就是每天多出来的半小时、一小时。


专注资料整理