3. Kotlin 增量编译优化:增量模式、类路径缓存与 KAPT 精简

Kotlin 编译速度在多模块 Android 项目中往往成为瓶颈。本章聚焦三个相互配合的优化手段:Kotlin 编译器增量模式、类路径缓存(Classpath Snapshot)以及禁用不必要的 KAPT 注解处理。三者从“减少输入范围”“复用已编译结果”“跳过不必要的注解处理”三个维度共同缩短编译时间。

3.1 Kotlin 编译器增量模式原理与配置

Kotlin 1.4+ 内置增量编译支持(默认在 Gradle 中启用)。其核心原理是:追踪每个源文件的依赖关系,当某个文件修改时,只重新编译该文件及其直接依赖者,而非整个模块。

属性默认值说明
kotlin.incrementaltrue(Gradle 6.8+)启用/禁用增量编译
kotlin.incremental.usePreciseJavaTrackingtrue(Kotlin 1.9+)更精细的 Java 变化追踪
kotlin.incremental.jsfalseJS 目标增量编译(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.includeCompileClasspathfalse不将编译类路径上的注解处理器纳入 KAPT(避免误触发)
kapt.useWorkerApitrue使用 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 调试与验证流程

  1. 查看构建扫描(Build Scan):在 Gradle 任务中加入 --scan,上传到 scans.gradle.com,观察 :compileKotlin 任务的执行时长和缓存命中。
  2. 本地日志:启用 org.gradle.performance.logging=warn 或 kotlin.incremental.log.level=info,检查日志中 “Incremental Kotlin compilation”、“Classpath snapshot reused” 等字样。
  3. 对比测试:先关闭增量优化(所有属性设为 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 三者协同流程图

Kotlin 增量编译优化协同流程 源文件变更(.kt / .java) Kotlin 增量编译器 文件级依赖 类路径快照缓存 复用 class 索引 KAPT 是否需要处理? 否(无需注解处理) 跳过 KAPT 是(启用 KAPT) KAPT 注解处理 产出 .class / .dex 优化策略对比 ✅ 增量编译器:减少编译范围 ✅ 类路径缓存:复用依赖索引 ✅ 禁用 KAPT:跳过无处理

3.7 部署建议

  • 将上述配置固化在项目根 gradle.properties 中,作为团队标准。
  • 在 CI 流水线中增加 --build-cache 和 Gradle 远程缓存,与增量编译互补。
  • 定期使用 ./gradlew build --scan 分析增量编译命中率,关注异常回退。