好玩系列 | 拥抱Kotlin Symbol Processing(KSP),项目实战
写在最前
这一篇,我们抱着拥抱新事物的心态,尝试一些新事物。笔者在这一次历程中,对三项事物进行了尝鲜:
- 手动迁移一个小规模的Gradle项目,由
Groovy Script
转为Kotlin Script
- Kotlin Symbol Processing
- Kotlin Poet
这次的 重点是KSP
,Kotlin Poet学习成本比较低,迁移 Kotlin Script
仅仅是比较繁琐。
既然要实战,那么就需要一个实际的项目来支持,笔者选取了个人的开源项目DaVinCi,.
关注笔者动态的读者可能注意到:笔者在过年时发布过一篇文章好玩系列:拥有它,XML文件少一半–更方便的处理View背景,
在这篇文章中,我们提到了一种 取代xml背景资源文件
的方案,并且提到之后会 实现Style机制
。本篇文章中,以此为目标,展开 KSP的实战过程
。
PS:对DaVinCi不了解并不影响本文内容的理解
如果读者对好玩系列感兴趣,建议点个关注,查看 关于好玩系列
了解笔者创作该系列的初衷。
KSP简介
在正式了解KSP之前,我们需要 复习
之前的知识:
- 编译期处理
- APT与KAPT
- Transformer
因为一些特定的需求,在项目进行编译的过程中,需要增加一定的处理,例如:生成源码文件并参与编译;修改编译的产物。
基于Gradle编译任务链中的 APT机制
,可以实现 Annotation Processor
,常见于 代码生成
, SPI机制实现
如AutoService ,也可以用来生成文档。
而APT仅支持Java源码,KAPT并没有 专门的注解处理器
,所以kotlin项目使用KAPT时,需要 生成代码桩
即Java Stub 再交由APT 进行处理。
基于Gradle编译任务链中的 Transformer机制
,可以动态的修改编译结果,例如利用 Javasist
,ASM
等字节码操纵框架 增加、修改字节码中的业务逻辑。
这导致Kotlin项目想要针对注解进行处理时,要么用力过猛,采用Transformer机制,要么就使用KAPT并牺牲时间。Transformer机制并无时间优势,若KAPT可以等价处理时,
Transformer机制往往呈现力大砖飞之势
那么顺理成章,KSP用于解决纯Kotlin项目下,无专门注解处理器的问题。
在KSP之前,Kotlin的编译存在有 Kotlin Compiler Plugin
了解更多
,下文简称KCP,KCP用于解决Kotlin 的关键词和注解的编译问题,例如 data class
,
而KCP的功能太过于强大,以至于需要 很大的学习成本
,而将问题局限于 注解处理
时,这一学习成本是多余的,于是出现了KSP,它基于KCP,但 屏蔽了KCP的细节
,
让我们 专注于注解处理的业务
KCP的复杂程度从其架构可见一斑
正式开始之前
在正式开始之前,我们再简要的阐明一下实战的目标:DaVinCi中可以定义 Style 和 StyleFactory:
推荐使用 StyleRegistry.Style.Factory,而不要直接定义 StyleRegistry.Style
@DaVinCiStyle(styleName = "btn_style.main")
class DemoStyle : StyleRegistry.Style("btn_style.main") {
init {
this.register(
state = State.STATE_ENABLE_FALSE,
expression = DaVinCiExpression.shape().rectAngle().solid("#80ff3c08").corner("10dp")
).register(
state = State.STATE_ENABLE_TRUE,
expression = DaVinCiExpression.shape().rectAngle().corner("10dp")
.gradient("#ff3c08", "#ff653c", 0)
)
}
}
@DaVinCiStyleFactory(styleName = "btn_style.main")
class DemoStyleFactory : StyleRegistry.Style.Factory() {
override val styleName: String = "btn_style.main"
override fun apply(style: StyleRegistry.Style) {
style.register(
state = State.STATE_ENABLE_FALSE,
expression = DaVinCiExpression.shape().rectAngle().solid("#80ff3c08").corner("10dp")
).register(
state = State.STATE_ENABLE_TRUE,
expression = DaVinCiExpression.shape().rectAngle().corner("10dp"