近日,android官方发布了Kotlin Symbol Processing(KSP)的alpha版本。
Announcing Kotlin Symbol Processing (KSP) Alpha
为什么使用KSP?
很多人在使用Kotlin时的痛点之一就是编译速度过慢。
很多常见的三方库都通过注解简化模板代码,例如Room、Dagger、Retrofit等,
Kotlin使用KAPT处理注解,KAPT没有专门的注解处理器,需要借助APT实现的,因为APT只能处理Java,所以KAPT需要生成APT可以解析的stub(Java代码),这影响了KAPT的性能,从而拖慢了Kotlin项目整体编译速度:
KSP正是在这个背景下诞生的,它基于Kotlin Compiler Plugin实现,随着Kotlinc的过程同步处理注解,不需要生成stub代码,编译速度是KAPT的2倍以上
KSP 与 Kotlin Compiler Plugin
Kotlin提供了编译器插件Compiler Plugin,可以在编译期分析AST、修改字节码产物等,Kotlin很多语法关键字以及注解都是基于KotlinCompilerPlugin实现的,例如data class
、@Parcelize
、kotlin-android-extension
等。
理论上Kotlin Compiler Plugin可以完全替代APT、transform等编译期工具,且效率更高,但是KCP的API学习成本高,需要了解一些编译器底层知识,普通开发者很难直接基于KCP处理注解。
一个的Compiler Plugin的开发需要若干过程:
KSP 的出现了屏蔽了对KCP了解,可以像使用KAPT一样,更愉快地进行注解处理
KSP使用初体验
SymbolProcessor
一般需要继承SymbolProcessor
来创建自己的KSP
interface SymbolProcessor {
fun init(options: Map<String, String>,
kotlinVersion: KotlinVersion,
codeGenerator: CodeGenerator,
logger: KSPLogger)
fun process(resolver: Resolver) // Let's focus on this
fun finish()
}
然后通过访问者模式,处理AST:
class HelloFunctionFinderProcessor : SymbolProcessor() {
...
val functions = mutableListOf<String>()
val visitor = FindFunctionsVisitor()
override fun process(resolver: Resolver) {
resolver.getAllFiles().map {
it.accept(visitor, Unit) }
}
inner class FindFunctionsVisitor : KSVisitorVoid() {
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
classDeclaration.getDeclaredFunctions().map {
it.accept(this, Unit) }
}
override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
functions.add(function)
}
override fun visitFile(file: KSFile, data: Unit) {
file.declarations.map {
it.accept(this, Unit) }
}
}
...
}
Samples
举几个例子展示一下KSP中的API是如何使用的
- 访问类中的所有成员方法
fun KSClassDeclaration.getDeclaredFunctions(): List<KSFunctionDeclaration> {
return this.declarations.filterIsInstance<KSFunctionDeclaration>()
}
- 判断一个类或者方法是否是局部类或局部方法
fun KSDeclaration.isLocal(): Boolean {
return this.parentDeclaration != null && this.parentDeclaration !is KSClassDeclaration
}
- 判断一个类成员是否对其他Declaration可见
fun KSDeclaration.isVisibleFrom(other: KSDeclaration): Boolean {
return when {
// locals are limited to lexical scope
this.isLocal() -> this.parentDeclaration == other
// file visibility or member
this.isPrivate() -> {
this.parentDeclaration == other.parentDeclaration
|| this.parentDeclaration == other
|| (
this.parentDeclaration == null
&& other.parentDeclaration == null
&& this.containingFile == other.containingFile
)
}
this.isPublic() -> true
this.isInternal() && other.containingFile != null && this.containingFile != null -> true
else -> false
}
}
- 解析注解
// Find out suppressed names in a file annotation:
// @file:kotlin.Suppress("Example1", "Example2")
fun KSFile.suppressedNames(): List<String> {
val ignoredNames = mutableListOf<String>()
annotations.forEach {
if (it.shortName.asString() == "Suppress" && it.annotationType.resolve()?.declaration?.qualifiedName?.asString() == "kotlin.Suppress") {
it.arguments.forEach {
(it.value as List<String>).forEach {
ignoredNames.add(it) }
}
}
}
return ignoredNames
}
使用KSP替代KAPT
KSP的目标很明确:开发者可以用近似KAPT的API处理注解,得到2倍以上的性能提升;使用者可以方便的将KAPT替换为KSP:
目前,已有不少三方库被要求增加对KSP的支持,并提上日程,相信随着KSP版本的逐渐稳定,未来这个趋势会越发明显。
Library | Status | Tracking issue for KSP |
---|---|---|
Room | In progress | Link |
Moshi | Experimentally supported | |
Auto Factory | Not yet supported | Link |
Dagger | Not yet supported | Link |
Hilt | Not yet supported | Link |
Glide | Not yet supported | Link |
如果你的项目也有对KAPT的需求,不妨试试KSP?