天天看點

Kotlin Symbol Processing(KSP)使用初體驗為什麼使用KSP?KSP 與 Kotlin Compiler PluginKSP使用初體驗使用KSP替代KAPT

Kotlin Symbol Processing(KSP)使用初體驗為什麼使用KSP?KSP 與 Kotlin Compiler PluginKSP使用初體驗使用KSP替代KAPT

近日,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項目整體編譯速度:

Kotlin Symbol Processing(KSP)使用初體驗為什麼使用KSP?KSP 與 Kotlin Compiler PluginKSP使用初體驗使用KSP替代KAPT

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的開發需要若幹過程:

Kotlin Symbol Processing(KSP)使用初體驗為什麼使用KSP?KSP 與 Kotlin Compiler PluginKSP使用初體驗使用KSP替代KAPT

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:

Kotlin Symbol Processing(KSP)使用初體驗為什麼使用KSP?KSP 與 Kotlin Compiler PluginKSP使用初體驗使用KSP替代KAPT

目前,已有不少三方庫被要求增加對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