【Kotlin】告别KAPT,拥抱KSP API

在这里插入图片描述

Kotlin Compiler Plugin


Kotlin提供了编译器插件Compiler Plugin,可以在编译期通过分析和修改AST修改最终生成的字节码。相对于APT或者Transform等方式效率更高。Kotlin很多语法关键字以及注解都是基于KotlinCompilerPlugin实现的,例如data class@Parcelize
kotlin-android-extension 等。

开发Compiler Plugin需要涉及一些编译器知识:例如需要了解编译器前、后端产物及相关API,有时需要还需要Gradle Plugin、IDEA Plugin等的配合,学习门槛较高,API也不够完善。普通开发者很难开发出自己的Compiler Plugin。

一个的Compiler Plugin的开发需要若干过程:

KSP API 的出现可以帮助开发者降低开发Compiler Plugin的门槛。

Kotlin Symbol Processing API


KSP API 是一套可用来开发轻量级Kotlin Compiler Plugin 的API, 他屏蔽了很多编译器细节,大大降低了使用门槛。KSP甚至不用JVM强相关,理论上开发出的编译器插件可以跨平台使用。

KSP API 可以基于Kotlin访问AST上的元素节点,例如classes、class members、functions、parameters等,如同访问 Kotlin reflection的KType一样,基于这些元素节点生成自己的代码,然后一同参与编译。

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) }
        }
    }
    ...
}

Examples


举几个使用KSP API访问AST的例子

  • 访问类中的所有成员方法
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
}

Compare To KAPT


KSP API相对于KAPT的主要优势有两个:

  1. KSP实在kotlinc的同时解析APT,而KAPT需要增加额外处理,所以前者的性能更高
  2. KAPT让然依托Java的AST,而KSP更加聚焦Kotlin的AST,对Kotlin更友好

为什么KAPT需要额外处理?KAPT发生在javac之前,所以需要基于源码而非字节码进行AST分析,重新写一套AST分析器显然工作量巨大。KAPT的做法是尽量复用APT的注解处理逻辑,APT只接受Java,这就需要增加将Kotlin预编成Java的处理,这个处理将会增加1/3的额外时间开销。

但是KAPT不依托Java的AST,所以不需要上述开销。性能方面的提升将会是KSP相对于KAPT的最大优势。


KSP :https://github.com/google/ksp

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fundroid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值