⚫ 历史版本:下方 Gradle 配置基于 Kotlin 1.9 与 KSP 1.x。新项目及 KSP2 迁移检查见 KSP 2 与 Kotlin 2.x:旧工程迁移前要核对什么。
KSP 基础
- KSP(Kotlin Symbol Processing)为处理器提供读取 Kotlin 符号并生成代码的 API。本文示例使用的是 KSP 1.x 实现。
- KAPT 与 KSP 的实现和兼容边界不同;不要把本文的旧版本号直接带到 Kotlin 2.x 工程。
项目配置
plugins {
kotlin("kapt")
id("com.google.devtools.ksp") version "1.9.24-1.0.20"
}
dependencies {
implementation("com.squareup.moshi:moshi:1.15.1")
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
}
Gradle 任务:./gradlew kspDebugKotlin。
自定义 Processor
1. 定义注解
@Target(AnnotationTarget.CLASS)
annotation class AutoRegister(val path: String)
2. 写 Processor
class AutoRegisterProcessor(env: SymbolProcessorEnvironment) : SymbolProcessor {
private val codeGenerator = env.codeGenerator
private val logger = env.logger
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(AutoRegister::class.qualifiedName!!)
symbols.filterIsInstance<KSClassDeclaration>().forEach { clazz ->
val path = clazz.getAnnotationsByType(AutoRegister::class).first().path
generateRegistry(clazz, path)
}
return emptyList()
}
private fun generateRegistry(clazz: KSClassDeclaration, path: String) {
val packageName = clazz.packageName.asString()
val fileName = "${clazz.simpleName.asString()}_Registry"
codeGenerator.createNewFile(Dependencies(false, clazz.containingFile!!), packageName, fileName).use { out ->
out.writer().use { writer ->
writer.appendLine("package $packageName")
writer.appendLine("object $fileName { const val PATH = \"$path\" }")
}
}
}
}
3. 注册 Processor
resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
com.example.ksp.AutoRegisterProcessorProvider
Provider:
class AutoRegisterProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return AutoRegisterProcessor(environment)
}
}
在 Android 中使用
- 生成的代码位于
build/generated/ksp/...。 - 通过
com.example.MyScreen_Registry.PATH获取配置。 - 可结合
ServiceLoader或Hilt注入实现自动注册。
性能与调试
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 处理器执行缓慢 | 遍历符号时重复扫描 | 使用 resolver.getSymbolsWithAnnotation 缓存结果 |
| 调试困难 | 无断点 | 使用 logger.warn 输出调试信息 |
| 增量编译失效 | 输出文件未声明依赖 | Dependencies(aggregating = false, ...) 指定输入 |
调试命令:./gradlew :app:kspDebugKotlin --info。
与 KAPT 对比
| 对比项 | KAPT | KSP |
|---|---|---|
| 支持语言 | Kotlin/Java | Kotlin |
| 性能 | 需生成 Java Stub,偏慢 | 原生处理 Kotlin AST,更快 |
| 生态支持 | 旧插件众多 | Moshi、Room、Hilt 已提供 KSP 版本 |
最佳实践
- 优先使用官方提供的 KSP 依赖(Room、Hilt、Moshi)。
- 自研 Processor 时控制生成文件数量,避免冗余代码。
- 在 CI 中开启
--no-build-cache定位问题,再逐步恢复增量配置。
实战案例:KSP 就是”Kotlin 版的 build_runner”
我是 Flutter 开发者,看到 KSP 的第一反应是:这不就是 Dart 的 build_runner 吗?确实是一个东西——编译期扫描注解、生成代码。Dart 侧 json_serializable 靠 build_runner,Kotlin 侧 Room/Hilt/Moshi 靠 KSP。
但 KSP 解决的是 KAPT 的原生痛点:KAPT 要先给 Kotlin 代码生成 Java stub(桩),再跑注解处理,等于编译两遍,又慢又吃内存。KSP 直接读 Kotlin 的符号,不走 Java 中间层。社区实测编译提速很明显,Room 官方文档现在默认给的就是 KSP 配置:
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation("androidx.room:room-runtime:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
}
注意 KSP 插件版本要和 Kotlin 版本对齐(比如 Kotlin 2.0.x 配 2.0.x-1.0.25 这种格式),对不齐直接报 Incompatible。Dart 的 build_runner 也有对等坑:json_serializable 和 build_runner 大版本必须配对,不然生成代码的行为会悄悄变。
我还手写过一个 Dart 代码生成器(ygking_json_to_dart_model,143 行,不用 build_runner,纯字符串模板拼)。对比下来:KSP/build_runner 适合”工程内、大规模、要增量编译”的场景;手写模板适合”一次性、调试用”的场景。工具没有高低,只有场景。
参考:Kotlin 官方文档 KSP 章节(kotlinlang.org);Room 官方文档 KSP 配置说明
总结
KSP 给 Kotlin 带来高效的编译期扩展能力。Android 项目可通过 KSP 实现自动路由、数据模型生成等,提高开发效率。掌握基本编写流程与调试方式,能让团队更好地利用编译期工具构建高质量基础设施。
