炉 VI · 回火CH 24进度 24/26

工具链:KSP2、编译器插件与 Amper

这一章讲的东西你不写,但它每天都在替你写代码。data class 的六件套、Compose 的重组逻辑、@Parcelize 的样板、Room 的 DAO 实现 —— 全都是别人的代码在编译期生成的。理解这一层,你就能回答一些很实际的问题:为什么我的构建这么慢(八成是 kapt)、为什么 Compose 的 @Composable 函数不能随便调用、为什么 kotlinx.serialization 能绕过类型擦除。Kotlin 的一个关键设计决定是:把语言的扩展能力开放给编译器插件 —— 而 Compose 就是这个决定最大的受益者。

kapt → KSP2编译器插件Amper版本矩阵

为什么 kapt 那么慢

kapt(Kotlin Annotation Processing Tool)是为了复用 Java 的注解处理器生态(Dagger、Room、Glide 都是 Java 时代的产物)而做的兼容层。它的工作方式,说出来你会觉得荒谬:

  1. 把你的 Kotlin 代码生成一份 Java 的「存根(stub)」(只有签名,没有方法体);
  2. 把这些假的 Java 文件喂给 Java 注解处理器;
  3. 处理器生成代码;
  4. 再把生成的代码和你的 Kotlin 一起编译。

那个「生成 Java 存根」的步骤,通常占掉整个构建时间的一大块 —— 而它产出的东西是一次性的、扔掉的。你的构建慢,一半的锅在这里。

KSP(Kotlin Symbol Processing) 是正解:它直接读 Kotlin 的符号信息,不生成任何 Java 存根,处理速度通常是 kapt 的两倍以上。KSP2 是它的新一代实现,跟 K2 编译器对齐。

◆ 锭 · 迁移清单(今天就能查)

build.gradle.kts 里搜 kapt。这几个库早就有 KSP 版本了

状态
Room✓ 支持 KSP(换一行配置的事)
Hilt / Dagger✓ 支持 KSP
Moshi✓ 支持 KSP(不过更好的选择是换 kotlinx.serialization —— 它是编译器插件,比任何注解处理器都快)
Glide✓ 支持 KSP

kapt 是构建速度的头号杀手,而迁移成本通常只是改几行 Gradle 配置。这是一个「几乎零成本、收益立竿见影」的改动 —— 如果你的项目还在用 kapt,这是本章最值钱的一句话。

更上一层:编译器插件

注解处理器(kapt/KSP)只能生成新文件,不能修改你已有的代码。而编译器插件可以直接改写 IR(中间表示) —— 它是真正的「语言扩展」。

你已经在用一大堆编译器插件了,只是没意识到:

插件它做了什么
Compose 编译器@Composable 函数改写成带 Composer 参数的形态、插入重组的记忆化逻辑、做稳定性推导(第 13 章)。Compose 不是一个库,它是一个语言扩展 —— 这就是为什么它能做到 React 用 hooks 规则约束的事,而 Kotlin 侧是编译期强制的
kotlinx.serialization为每个 @Serializable在编译期生成序列化器 —— 所以它不需要反射、不需要 TypeToken,也就绕过了类型擦除(第 5 章)
@Parcelize自动生成 Parcelable 的样板(那一百行 writeToParcel,还记得吗)
all-open / no-argSpring、JPA 需要类可继承、需要无参构造 —— 插件替你加上,不用把整个项目改成 open
Power-assert断言失败时,打印出表达式每一部分的值(而不是干巴巴一句 "expected true but was false")—— 测试调试神器,2.4 起有了新的运行时和 @PowerAssert 注解
data class 本身严格说这是编译器内建的,但性质一样:编译期代码生成

为什么这件事重要?因为它解释了 Kotlin 的一个战略选择:语言核心保持小巧,把扩展能力开放出去。—— 于是 Compose 这种「需要语言级支持」的框架,不需要修改 Kotlin 语言就能实现。对比一下:React 的 hooks 规则只能靠 lint 规则和运行时警告来保证,而 Compose 的规则是编译器强制的。

(顺带:这也是第 9、11 章那两块地基的延伸 —— Kotlin 一直在做同一件事:把语言能力交给库作者。

Amper:JetBrains 想终结 Gradle 的痛

说句实话:Gradle 是 Android 开发体验里最糟的部分。一个「配置构建」的任务,变成了「用一门图灵完备的语言写一个程序,去配置另一个程序」—— 于是构建脚本变得没人看得懂,构建慢,而且出问题时无从下手。

Amper 是 JetBrains 的答案:声明式的构建配置(YAML/TOML 风格),意图明确、IDE 能理解、不需要你懂任务图。KotlinConf 26 上它被确立为 Kotlin Toolchain 的核心

product: android/app

dependencies:
  - io.ktor:ktor-client-core:3.0.0
  - $compose.foundation

settings:
  compose: enabled
  kotlin:
    languageVersion: 2.4

现在该怎么办?—— 保持关注,别急着迁。生产项目的构建系统是最不该冒险的地方。但如果你要起一个新的小项目、或者做一个内部工具,试一下 Amper 会让你重新想起「构建配置本来应该是简单的」。

版本矩阵:一个绕不开的现实

Kotlin、Gradle、AGP、JDK、Compose 编译器 —— 这五个东西互相有版本约束,而且约束是硬的。这是升级时最容易卡住的地方:

  • Kotlin 2.4 要求 AGP ≥ 8.5.2,兼容 Gradle 7.6.3—9.5.0。
  • Compose 编译器从 Kotlin 2.0 起和 Kotlin 版本号同步发布(以前是独立版本号,还要查兼容表 —— 那个痛苦已经结束了)。
  • AGP 的新版本通常要求更高的 JDK

实用建议:升级时一次只动一个(先 Gradle,再 AGP,再 Kotlin),每一步都跑一次完整构建。同时动三个,出了问题你会不知道该回滚哪个。

⚠ 炉渣 · 构建慢的常见元凶(按概率排)
  1. kapt(上面说的存根生成)—— 换 KSP
  2. 没开配置缓存 / 构建缓存org.gradle.configuration-cache=true
  3. 还在 Kotlin 1.9(没吃到 K2 的红利)
  4. 模块划分不合理:一个巨大的 app 模块,改一行全量重编
  5. 滥用 api 依赖(应该用 implementation)—— 依赖会传递,改一个底层模块导致上面全部重编

这五条解决完,你的构建时间通常能砍掉一半以上 —— 而它们全都不需要改一行业务代码。

✚ 动手锻打

1. kapt如果还在用,今天就把 Room / Hilt / Moshi 迁到 KSP。跑 ./gradlew clean assembleDebug --profile 对比前后耗时 —— 这个数字会让你的团队闭嘴

2. 检查 gradle.properties:配置缓存开了吗?org.gradle.caching 开了吗?

3. 如果项目里在用 Gson/Moshi 做 JSON:评估一次迁到 kotlinx.serialization。收益是三重的 —— 更快(编译期生成,无反射)、类型更安全(不怕擦除)、KMP 可用。

4. 试一次 Power-assert:给一个失败的断言开上它,看看报错信息的差别。它会改变你写测试断言的方式。

下一章:疆域 —— 你的 Kotlin 技能现在能覆盖多大地盘。KMP、Compose Multiplatform、Swift Export,以及用 Kotlin 写 AI Agent。