3. Kotlin 增量编译优化:增量模式、类路径缓存与 KAPT 精简
Kotlin 编译速度在多模块 Android 项目中往往成为瓶颈。本章聚焦三个相互配合的优化手段:Kotlin 编译器增量模式、类路径缓存(Classpath Snapshot)以及禁用不必要的 KAPT 注解处理。三者从“减少输入范围”“复用已编译结果”“跳过不必要的注解处理”三个维度共同缩短编译时间。
3.1 Kotlin 编译器增量模式原理与配置
Kotlin 1.4+ 内置增量编译支持(默认在 Gradle 中启用)。其核心原理是:追踪每个源文件的依赖关系,当某个文件修改时,只重新编译该文件及其直接依赖者,而非整个模块。
| 属性 | 默认值 | 说明 |
|---|---|---|
kotlin.incremental | true(Gradle 6.8+) | 启用/禁用增量编译 |
kotlin.incremental.usePreciseJavaTracking | true(Kotlin 1.9+) | 更精细的 Java 变化追踪 |
kotlin.incremental.js | false | JS 目标增量编译(Android 不用) |
验证生效方法:在
gradle.properties 中设置 -Dkotlin.incremental.log.level=verbose,观察构建日志中 “Incremental compilation” 字样。
# gradle.properties
kotlin.incremental=true
kotlin.incremental.usePreciseJavaTracking=true
# 开启日志(可选,仅调试用)
-Dkotlin.incremental.log.level=verbose
3.2 类路径缓存(Classpath Snapshot)
当模块的依赖(如第三方库)未变化时,Kotlin 编译器可以缓存其类路径的快照,避免每次编译时全盘扫描所有依赖的 class 文件。该特性由 kotlin.incremental.classpath.snapshot 控制。
- 原理:为每个模块生成一个“类路径指纹”,包含所有依赖项的时间戳、大小、哈希。指纹不变则复用上一次的字节码索引。
- 配置:Gradle 7.4+ 及 Kotlin 1.7.0+ 默认启用。
- 效果:在依赖库稳定的开发周期中,可减少 20%~40% 的编译前期分析时间。
# gradle.properties(通常默认已开启)
kotlin.incremental.classpath.snapshot=true
# 如果遇到缓存命中问题,可尝试关闭并对比
kotlin.incremental.classpath.snapshot=false
3.3 禁用不必要的 KAPT 注解处理
KAPT(Kotlin Annotation Processing Tool)会启动 javac 注解处理器,速度较慢。很多模块实际上并不需要 KAPT,或者只需要部分处理器。
| 配置项 | 推荐值 | 作用 |
|---|---|---|
kapt.includeCompileClasspath | false | 不将编译类路径上的注解处理器纳入 KAPT(避免误触发) |
kapt.useWorkerApi | true | 使用 Worker API 并行处理注解,适合多核机器 |
常见错误:有些库(如 Dagger、Room)必须通过 KAPT 生成代码,禁用后会导致编译失败。请在 build.gradle 中按模块逐一评估。
// 在每个 module 的 build.gradle.kts 中按需配置
kapt {
includeCompileClasspath = false
useWorkerApi = true
// 显式列出需要的注解处理器(可选)
correctErrorTypes = true
}
// 若模块完全不需要注解处理,可删除 kapt 插件
plugins {
// 不要 id("kotlin-kapt")
id("org.jetbrains.kotlin.android") version "1.9.22"
}
3.4 调试与验证流程
- 查看构建扫描(Build Scan):在 Gradle 任务中加入
--scan,上传到 scans.gradle.com,观察:compileKotlin任务的执行时长和缓存命中。 - 本地日志:启用
org.gradle.performance.logging=warn或kotlin.incremental.log.level=info,检查日志中 “Incremental Kotlin compilation”、“Classpath snapshot reused” 等字样。 - 对比测试:先关闭增量优化(所有属性设为 false),记录一次全量编译时间;再依次打开每个优化,记录增量修改后的编译时间。
// 示例:在构建命令中加入 ——scan
./gradlew assembleDebug --scan -Dkotlin.incremental.log.level=verbose
3.5 常见错误与稳定性优化
- 增量编译不稳定:有时 Kotlin 编译器检测到复杂依赖变更后回退到全量编译(日志中出现 “no incremental compilation”)。可尝试升级 Kotlin/Gradle 版本,或设置
kotlin.incremental.usePreciseJavaTracking=true。 - 类路径缓存失效:频繁改动的本地模块会不断使缓存失效。建议将经常修改的模块单独配置
kotlin.incremental.classpath.snapshot=false。 - KAPT 误触发:若某个模块虽然声明了 kapt 插件但实际没有注解处理器,构建依然会启动 KAPT 进程。使用
kapt.includeCompileClasspath=false并移除无用注解处理器依赖。 - Gradle 守护进程内存不足:增量编译会缓存更多元数据,可能增大守护进程内存压力。在
gradle.properties中增大org.gradle.jvmargs=-Xmx4096m。
性能基线参考:某 30+ 子模块项目,同时启用三项优化后,增量编译从 120 秒降至 25 秒,降幅约 79%。
3.6 三者协同流程图
3.7 部署建议
- 将上述配置固化在项目根
gradle.properties中,作为团队标准。 - 在 CI 流水线中增加
--build-cache和 Gradle 远程缓存,与增量编译互补。 - 定期使用
./gradlew build --scan分析增量编译命中率,关注异常回退。