
近日,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?
KSP :https://github.com/google/ksp