26、自定义Gradle插件:集成KSP/KAPT、配置注解处理器依赖
说实话,注解处理器在Android项目里已经是个标配了。不管是ButterKnife、Glide还是Room,背后都离不开APT或者KSP。但问题来了——每次新建项目都要手动配一堆依赖,版本号还容易写错,团队里有人漏配了某个参数,编译直接挂掉。
我个人习惯是把这些重复劳动封装成一个Gradle插件。你想想看,一行apply plugin,所有注解处理器自动就位,多省心。今天我们就来聊聊怎么自定义一个Gradle插件,把KSP和KAPT的集成工作彻底自动化。
为什么需要自定义插件?
我在项目中遇到过好几次这样的场景:项目里有十几个模块,每个模块都要配Room、Glide、Moshi的注解处理器。有人漏了kapt { correctErrorTypes = true },有人版本号写成了旧版,编译报错排查半天。
说白了,手动配置就是容易出错。自定义插件能帮你做到三件事:
- 统一版本管理——所有注解处理器版本号写在插件里,一处修改全局生效
- 自动配置参数——KAPT的
correctErrorTypes、KSP的blockOtherPlugins,插件帮你写好 - 按需启用——根据模块类型(application/library)自动判断是否启用KAPT
插件项目结构
我们先搭一个标准的Gradle插件项目。嗯,这里要注意,插件本身要用Java或者Kotlin写,我推荐用Kotlin,毕竟跟Android项目语言一致。
// 项目根目录
annotation-processor-plugin/
├── build.gradle.kts
├── src/
│ └── main/
│ └── kotlin/
│ └── com/example/plugin/
│ ├── AnnotationProcessorPlugin.kt
│ └── AnnotationProcessorExtension.kt
└── src/
└── main/
└── resources/
└── META-INF/
└── gradle-plugins/
└── com.example.annotation-processor.properties
插件的build.gradle.kts要引入java-gradle-plugin和kotlin插件。我个人习惯用implementation(gradleApi())来依赖Gradle API,这样最干净。
// build.gradle.kts
plugins {
`java-gradle-plugin`
kotlin("jvm") version "1.9.0"
}
dependencies {
implementation(gradleApi())
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.0")
}
gradlePlugin {
plugins {
register("annotationProcessor") {
id = "com.example.annotation-processor"
implementationClass = "com.example.plugin.AnnotationProcessorPlugin"
}
}
}
核心逻辑:集成KSP和KAPT
插件的主类要干两件事:一是把KSP/KAPT插件apply到目标项目,二是配置注解处理器的依赖。我写了一个简化版本,你看一下核心思路:
// AnnotationProcessorPlugin.kt
class AnnotationProcessorPlugin : Plugin<Project> {
override fun apply(project: Project) {
// 1. 创建扩展,让用户能配置版本号
val extension = project.extensions.create(
"annotationProcessor",
AnnotationProcessorExtension::class.java
)
// 2. 判断是否Android项目
if (!project.plugins.hasPlugin("com.android.application") &&
!project.plugins.hasPlugin("com.android.library")) {
return
}
// 3. 根据配置选择KAPT或KSP
project.afterEvaluate {
val config = extension.get()
if (config.useKsp) {
applyKsp(project, config)
} else {
applyKapt(project, config)
}
}
}
private fun applyKsp(project: Project, config: AnnotationProcessorConfig) {
// 应用KSP插件
project.plugins.apply("com.google.devtools.ksp")
// 配置依赖
project.dependencies.add("ksp", "androidx.room:room-compiler:${config.roomVersion}")
project.dependencies.add("ksp", "com.github.bumptech.glide:compiler:${config.glideVersion}")
}
private fun applyKapt(project: Project, config: AnnotationProcessorConfig) {
// 应用KAPT插件
project.plugins.apply("org.jetbrains.kotlin.kapt")
// 配置KAPT参数
project.extensions.configure<KaptExtension>("kapt") {
correctErrorTypes = config.correctErrorTypes
}
// 添加依赖
project.dependencies.add("kapt", "androidx.room:room-compiler:${config.roomVersion}")
project.dependencies.add("kapt", "com.github.bumptech.glide:compiler:${config.glideVersion}")
}
}
你可能会问,为什么用afterEvaluate?因为插件apply的时候,Android的buildTypes和productFlavors还没配置好,直接加依赖可能会漏掉某些变体。等评估完再配,稳一点。
扩展配置:让用户自定义
插件不能写死版本号,得留个口子让用户改。我设计了一个AnnotationProcessorExtension,用open属性让用户覆盖:
// AnnotationProcessorExtension.kt
open class AnnotationProcessorExtension {
var useKsp: Boolean = false
var correctErrorTypes: Boolean = true
var roomVersion: String = "2.6.0"
var glideVersion: String = "4.15.1"
var moshiVersion: String = "1.15.0"
fun get(): AnnotationProcessorConfig {
return AnnotationProcessorConfig(
useKsp = useKsp,
correctErrorTypes = correctErrorTypes,
roomVersion = roomVersion,
glideVersion = glideVersion,
moshiVersion = moshiVersion
)
}
}
data class AnnotationProcessorConfig(
val useKsp: Boolean,
val correctErrorTypes: Boolean,
val roomVersion: String,
val glideVersion: String,
val moshiVersion: String
)
用户在项目的build.gradle.kts里这样用:
plugins {
id("com.example.annotation-processor")
}
annotationProcessor {
useKsp = true
roomVersion = "2.6.1"
glideVersion = "4.16.0"
}
你看,一行插件声明,三行配置,所有注解处理器自动就位。团队里再也不会有人漏配了。
避坑指南:我曾经踩过的坑
我曾经在配置KSP的时候,忘了设置blockOtherPlugins参数,结果KSP和KAPT同时生效,编译直接报重复类定义。后来我加了一个检查:如果用户同时启用了KSP和KAPT,插件直接抛异常。
还有一个坑:KSP的依赖配置名是ksp,不是kapt。如果你写错了,依赖不会报错,但注解处理器根本不生效。我建议在插件里加一个日志输出,打印出最终添加的依赖列表,方便排查。
知识体系总览
下面这张图把整个插件的设计思路串起来了,你看一眼就能明白各个模块的关系:
发布与使用
插件写好了,怎么发布?我一般用maven-publish插件发布到公司内部的Nexus仓库。你也可以发布到Gradle Plugin Portal,让全世界的人用。
// 发布配置
plugins {
`maven-publish`
}
publishing {
publications {
create<MavenPublication>("plugin") {
from(components["java"])
groupId = "com.example"
artifactId = "annotation-processor-plugin"
version = "1.0.0"
}
}
repositories {
maven {
url = uri("https://your-nexus.com/repository/maven-releases/")
}
}
}
项目里引用的时候,先在settings.gradle.kts里声明插件源:
pluginManagement {
repositories {
maven { url = uri("https://your-nexus.com/repository/maven-releases/") }
gradlePluginPortal()
}
}
然后在模块的build.gradle.kts里直接apply:
plugins {
id("com.example.annotation-processor") version "1.0.0"
}
gradle.properties里,方便统一升级。比如写一个annotationProcessorVersion=1.0.0,然后在build.gradle.kts里用version = annotationProcessorVersion引用。
总结
自定义Gradle插件来集成KSP/KAPT,说白了就是把重复劳动自动化。你只需要写一次插件逻辑,团队里所有人都能受益。而且插件可以随着项目演进不断迭代——比如新增一个注解处理器、调整默认版本号,改一行代码,所有模块自动同步。
我个人觉得,这种"基础设施"类的投入,性价比非常高。花半天时间写个插件,能省下未来无数个"编译报错排查"的下午。嗯,如果你还没试过,建议从一个小模块开始,把Room或者Glide的注解处理器抽出来,感受一下自动化的快感。
公众号:蓝海资料掘金营,微信deep3321