跳转到正文
YGJoon 技术博客

Android Kotlin KSP 编译期代码生成

发布于:更新于:
·977 字 · 约 2 分钟·作者:
保留 Kotlin 1.9 与 KSP 1.x 的 Processor 示例和旧项目排查线索;新工程请先阅读 KSP2 与 Kotlin 2.x 更新说明。
Android Kotlin KSP 编译期代码生成文章封面
目录

⚫ 历史版本:下方 Gradle 配置基于 Kotlin 1.9 与 KSP 1.x。新项目及 KSP2 迁移检查见 KSP 2 与 Kotlin 2.x:旧工程迁移前要核对什么。

KSP 基础

项目配置

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 中使用

性能与调试

问题说明解决方案
处理器执行缓慢遍历符号时重复扫描使用 resolver.getSymbolsWithAnnotation 缓存结果
调试困难无断点使用 logger.warn 输出调试信息
增量编译失效输出文件未声明依赖Dependencies(aggregating = false, ...) 指定输入

调试命令:./gradlew :app:kspDebugKotlin --info。

与 KAPT 对比

对比项KAPTKSP
支持语言Kotlin/JavaKotlin
性能需生成 Java Stub,偏慢原生处理 Kotlin AST,更快
生态支持旧插件众多Moshi、Room、Hilt 已提供 KSP 版本

最佳实践

  1. 优先使用官方提供的 KSP 依赖(Room、Hilt、Moshi)。
  2. 自研 Processor 时控制生成文件数量,避免冗余代码。
  3. 在 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 实现自动路由、数据模型生成等,提高开发效率。掌握基本编写流程与调试方式,能让团队更好地利用编译期工具构建高质量基础设施。


分享这篇文章:


相关文章

上一篇
Android Kotlin Hilt 依赖注入实战
下一篇
Android Kotlin 多模块资源与路由管理