注解处理器与KSP:APT配置、KSP配置、增量处理、处理器选项、Room/Glide等实战
说到注解处理器,很多Android开发者的第一反应就是「慢」。确实,我早期做项目时,每次改个注解就要等几十秒重新编译,那种感觉真的很抓狂。后来KSP(Kotlin Symbol Processing)出来了,我才发现原来注解处理可以这么快。
这一章,我们就来聊聊APT和KSP的配置、增量处理,以及Room、Glide这些常用库的实战用法。
APT vs KSP:到底选哪个?
先说说APT(Annotation Processing Tool)。它是Java时代的产物,处理的是Java源码。Kotlin项目用APT,其实有个隐藏问题——Kotlin代码会被编译成Java存根,然后APT再处理这些存根。嗯,你想想看,多了一层转换,速度能快吗?
KSP就不一样了。它直接解析Kotlin源码,省掉了中间步骤。我在项目中实测过,同样的Room数据库代码,KSP比APT快了将近2倍。尤其是大型项目,这个差距会更明显。
APT配置:老派但稳定
如果你还在用APT,配置方式大概是这样的:
// 项目的 build.gradle.kts
plugins {
id("org.jetbrains.kotlin.kapt") version "1.9.0"
}
dependencies {
// 声明处理器
kapt("androidx.room:room-compiler:2.6.0")
// 普通依赖
implementation("androidx.room:room-runtime:2.6.0")
}
这里有个坑,我踩过好几次——kapt依赖必须写在dependencies块里,不能写在buildscript里。否则编译器会一脸懵,根本找不到处理器。
KSP配置:又快又简单
KSP的配置就清爽多了。先加插件:
// 项目的 build.gradle.kts
plugins {
id("com.google.devtools.ksp") version "1.9.0-1.0.13"
}
dependencies {
// 直接用 ksp 替代 kapt
ksp("androidx.room:room-compiler:2.6.0")
implementation("androidx.room:room-runtime:2.6.0")
}
你看,就改了一个关键字。但背后的性能提升是实打实的。我个人习惯是,只要库支持KSP,我就优先用KSP。目前Room、Glide、Moshi这些主流库都已经支持了。
增量处理:别再全量编译了
增量处理,说白了就是「只编译改动的部分」。默认情况下,注解处理器每次都会扫描所有文件。项目大了以后,每次改一行代码都要等半分钟,真的很崩溃。
开启增量处理其实很简单,在gradle.properties里加一行:
kapt.incremental.apt=true
对于KSP,它默认就是增量的,不需要额外配置。这也是KSP的一大优势。
不过要注意,增量处理不是万能的。如果你的处理器里写了全局状态(比如静态Map),增量处理可能会出问题。我遇到过的情况是,第一次编译正常,第二次编译就报「重复元素」错误。后来排查发现,是处理器里缓存了上一次的结果,没有清空。
处理器选项:给处理器传参数
有时候我们需要给注解处理器传一些参数,比如Room的数据库版本号。这就要用到处理器选项了。
在APT中,通过kapt { arguments { ... } }来配置:
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.incremental", "true")
}
}
KSP的配置方式类似:
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.incremental", "true")
}
这些参数会被传递到处理器的init()方法里。你可以在自定义处理器中通过processingEnv.getOptions()获取。
Room实战:从APT迁移到KSP
Room是注解处理器的典型应用。我们来看看怎么从APT迁移到KSP。
第一步,替换插件和依赖:
// 替换 kapt 为 ksp
plugins {
id("com.google.devtools.ksp") version "1.9.0-1.0.13"
}
dependencies {
ksp("androidx.room:room-compiler:2.6.0")
implementation("androidx.room:room-runtime:2.6.0")
implementation("androidx.room:room-ktx:2.6.0")
}
第二步,配置处理器选项:
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.incremental", "true")
}
第三步,代码完全不用改。Room的注解(@Entity、@Dao、@Database)在KSP下完全兼容。
迁移完成后,你会发现编译速度快了很多。我自己的项目,从APT切到KSP后,增量编译时间从15秒降到了6秒。
Glide实战:注解处理器的另一种用法
Glide的注解处理器主要用于生成AppGlideModule的实现类。配置方式如下:
dependencies {
// APT方式
kapt("com.github.bumptech.glide:compiler:4.15.1")
// KSP方式(Glide 4.15+ 支持)
ksp("com.github.bumptech.glide:ksp:4.15.1")
implementation("com.github.bumptech.glide:glide:4.15.1")
}
然后在代码里写一个AppGlideModule:
@GlideModule
class MyAppGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setDefaultRequestOptions(
RequestOptions().format(DecodeFormat.PREFER_RGB_565)
)
}
}
编译后,Glide会自动生成GlideApp类,你就可以用GlideApp.with(this).load(url).into(imageView)了。
自定义注解处理器:从零开始
如果你需要自己写一个注解处理器,流程大概是这样的:
第一步,创建一个Java/Kotlin模块,专门放处理器代码。
第二步,实现AbstractProcessor类:
class MyProcessor : AbstractProcessor() {
override fun getSupportedAnnotationTypes(): Set<String> {
return setOf(MyAnnotation::class.java.canonicalName)
}
override fun process(
annotations: Set<TypeElement>,
roundEnv: RoundEnvironment
): Boolean {
// 处理注解
roundEnv.getElementsAnnotatedWith(MyAnnotation::class.java).forEach { element ->
// 生成代码
}
return true
}
}
第三步,注册处理器。在META-INF/services/javax.annotation.processing.Processor文件中写上处理器的全限定名。
嗯,这里要注意,KSP的自定义处理器写法不太一样,它用的是SymbolProcessor接口。不过核心逻辑是一样的——解析注解,生成代码。
SVG:注解处理核心流程
常见问题与避坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 编译报错「找不到符号」 | 处理器生成的代码没有被编译 | 检查依赖配置,确保处理器在annotationProcessor/ksp中 |
| 增量编译失效 | 处理器里用了静态变量或全局状态 | 避免静态变量,或在每次处理前清空 |
| KSP和APT混用冲突 | 同一个库同时用了kapt和ksp | 统一用一种方式,不要混用 |
| Room数据库迁移失败 | schema文件路径配置错误 | 检查room.schemaLocation参数是否正确 |
最后说一句,注解处理器这块,配置本身不难,难的是排查问题。我建议你在切换KSP时,先在一个小模块上试,确认没问题再全量迁移。这样即使出问题,影响范围也小。
好了,这一章的内容就到这里。记住,KSP是未来,APT是过去。能切就切,别犹豫。