一、KSP
在进行Android利用开发时,不少人吐槽 Kotlin 的编译速度慢,而KAPT 便是拖慢编译的首恶之一。咱们晓得,Android的很多库都会应用注解简化模板代码,例如 Room、Dagger、Retrofit 等,而默认状况下Kotlin 应用的是 KAPT 来解决注解的。KAPT没有专门的注解处理器,须要借助APT实现的,因而须要学生成 APT 可解析的 stub (Java代码),这拖慢了 Kotlin 的整体编译速度。
KSP 正是在这个背景下诞生的,它基于 Kotlin Compiler Plugin(简称KCP) 实现,不须要生成额定的 stub,编译速度是 KAPT 的 2 倍以上。除了 大幅提高 Kotlin 开发者的构建速度,该工具还提供了对 Kotlin/Native 和 Kotlin/JS 的反对。
二、KSP 与 KCP
这里提到了Kotlin Compiler Plugin ,KCP是在 kotlinc 过程中提供 Hook 机会,能够在期间解析 AST、批改字节码产物等,Kotlin 的不少语法糖都是 KCP 实现的。例如, data class、 @Parcelize、kotlin-android-extension 等,现在火爆的 Jetpack Compose也是借助 KCP 实现的。
实践上来说, KCP 的能力是 KAPT 的超集,齐全能够代替 KAPT 以晋升编译速度。然而 KCP 的开发成本太高,波及 Gradle Plugin、Kotlin Plugin 等的应用,API 波及一些编译器常识的理解,个别开发者很难把握。一个规范 KCP 架构如下所示。
上图中波及到几个具体的概念:
- Plugin:Gradle 插件用来读取 Gradle 配置传递给 KCP(Kotlin Plugin);
- Subplugin:为 KCP 提供自定义 Kotlin Plugin 的 maven 库地址等配置信息;
- CommandLineProcessor:将参数转换为 Kotlin Plugin 可辨认参数;
- ComponentRegistrar:注册 Extension 到 KCP 的不同流程中;
- Extension:实现自定义的Kotlin Plugin性能;
KSP 简化了KCP的整个流程,开发者无需理解编译器工作原理,解决注解等老本也变得像 KAPT 一样低。
三、KSP 与 KAPT
KSP 顾名思义,在 Symbols 级别对 Kotlin 的 AST 进行解决,拜访类、类成员、函数、相干参数等类型的元素。能够类比 PSI 中的 Kotlin AST,构造如下图。
能够看到,一个 Kotlin 源文件经 KSP 解析后失去的 Kotlin AST如下所示。
KSFile packageName: KSName fileName: String annotations: List<KSAnnotation> (File annotations) declarations: List<KSDeclaration> KSClassDeclaration // class, interface, object simpleName: KSName qualifiedName: KSName containingFile: String typeParameters: KSTypeParameter parentDeclaration: KSDeclaration classKind: ClassKind primaryConstructor: KSFunctionDeclaration superTypes: List<KSTypeReference> // contains inner classes, member functions, properties, etc. declarations: List<KSDeclaration> KSFunctionDeclaration // top level function simpleName: KSName qualifiedName: KSName containingFile: String typeParameters: KSTypeParameter parentDeclaration: KSDeclaration functionKind: FunctionKind extensionReceiver: KSTypeReference? returnType: KSTypeReference parameters: List<KSValueParameter> // contains local classes, local functions, local variables, etc. declarations: List<KSDeclaration> KSPropertyDeclaration // global variable simpleName: KSName qualifiedName: KSName containingFile: String typeParameters: KSTypeParameter parentDeclaration: KSDeclaration extensionReceiver: KSTypeReference? type: KSTypeReference getter: KSPropertyGetter returnType: KSTypeReference setter: KSPropertySetter parameter: KSValueParameter
相似的, APT/KAPT 则是对 Java AST 的形象,咱们能够找到一些对应关系,比方 Java 应用 Element 形容包、类、办法或者变量等, KSP 中应用 Declaration。
Java/APT | Kotlin/KSP | 形容 |
---|---|---|
PackageElement | KSFile | 一个包程序元素,提供对无关包及其成员的信息的拜访 |
ExecuteableElement | KSFunctionDeclaration | 某个类或接口的办法、构造方法或初始化程序(动态或实例),包含正文类型元素 |
TypeElement | KSClassDeclaration | 一个类或接口程序元素。提供对无关类型及其成员的信息的拜访。留神,枚举类型是一品种,而注解类型是一种接口 |
VariableElement | KSVariableParameter / KSPropertyDeclaration | 一个字段、enum 常量、办法或构造方法参数、局部变量或异样参数 |
Declaration 之下还有 Type 信息 ,比方函数的参数、返回值类型等,在 APT 中应用 TypeMirror 承载类型信息 ,KSP 中具体的能力由 KSType 实现。
四、KSP 入口SymbolProcessorProvider
KSP的入口在SymbolProcessorProvider ,代码如下:
interface SymbolProcessorProvider { fun create(environment: SymbolProcessorEnvironment): SymbolProcessor}
SymbolProcessorEnvironment 次要用于获取 KSP 运行时的依赖,注入到 Processor:
interface SymbolProcessor { fun process(resolver: Resolver): List<KSAnnotated> // Let's focus on this fun finish() {} fun onError() {}}
process() 办法须要提供一个 Resolver , 解析 AST 上的 symbols,Resolver 应用访问者模式去遍历 AST。如下,Resolver 应用 FindFunctionsVisitor 找出以后 KSFile 中 top-level 的 function 以及 Class 成员办法。
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) } } } ... class Provider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = ... }}
五、疾速上手
5.1 创立processor
首先,创立一个空的gradle工程。
而后,在根我的项目中指定Kotlin插件的版本,以便在其余我的项目模块中应用,比方。
plugins { kotlin("jvm") version "1.6.0" apply false}buildscript { dependencies { classpath(kotlin("gradle-plugin", version = "1.6.0")) }}
不过,为了对立我的项目中Kotlin的版本,能够在gradle.properties文件中进行对立的配置。
kotlin.code.style=officialkotlinVersion=1.6.0kspVersion=1.6.0-1.0.2
接着,增加一个用于承载处理器的模块。并在模块的build.gradle.kts文件中增加如下脚步。
plugins { kotlin("jvm")}repositories { mavenCentral()}dependencies { implementation("com.google.devtools.ksp:symbol-processing-api:1.6.0-1.0.2")}
接着,咱们须要实现com.google.devtools.ksp.processing.SymbolProcessor和com.google.devtools.ksp.processing.SymbolProcessorProvider。SymbolProcessorProvider的实现作为一个服务加载,以实例化实现的SymbolProcessor。应用时须要留神以下几点:
- 应用SymbolProcessorProvider.create()来创立一个SymbolProcessor。processor须要的依赖则能够通过SymbolProcessorProvider.create()提供的参数进行传递。
- 次要逻辑应该在SymbolProcessor.process()办法中执行。
- 应用resoler.getsymbolswithannotation()来取得咱们想要解决的内容,前提是给出正文的齐全限定名称,比方
com.example.annotation.Builder
。 - KSP的一个常见用例是实现一个定制的拜访器,接口
com.google.devtools.ksp.symbol.KSVisitor
,用于操作符号。 - 无关SymbolProcessorProvider和SymbolProcessor接口的应用示例,请参见示例我的项目中的以下文件:
src/main/kotlin/BuilderProcessor.kt
和src/main/kotlin/TestProcessor.kt
。 - 编写本人的处理器之后,在
resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
中蕴含处理器提供商的齐全限定名,将其注册到包中。
5.2 应用processor
5.2.1 应用Kotlin DSL
再创立一个模块,蕴含处理器须要解决的工作。而后,在build.gradle.kts文件中增加如下代码。
pluginManagement { repositories { gradlePluginPortal() }}
在新模块的build.gradle中,咱们次要实现以下事件:
- 利用带有指定版本的com.google.devtools.ksp插件。
- 将ksp增加到依赖项列表中
比方:
plugins { id("com.google.devtools.ksp") version kspVersion kotlin("jvm") version kotlinVersion }
运行./gradlew
命令进行构建,能够在build/generated/source/ksp
下找到生成的代码。上面是一个build.gradle.kts将KSP插件利用到workload的示例。
plugins { id("com.google.devtools.ksp") version "1.6.0-1.0.2" kotlin("jvm") }version = "1.0-SNAPSHOT"repositories { mavenCentral()}dependencies { implementation(kotlin("stdlib-jdk8")) implementation(project(":test-processor")) ksp(project(":test-processor"))}
5.2.2 应用Groovy
在您的我的项目中构建。Gradle文件增加了一个蕴含KSP插件的插件块
plugins { id "com.google.devtools.ksp" version "1.5.31-1.0.0"}
而后,在dependencies增加如下依赖。
dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(":test-processor") ksp project(":test-processor")}
SymbolProcessorEnvironment提供了processors选项,选项在gradle构建脚本中指定。
ksp { arg("option1", "value1") arg("option2", "value2") ... }
5.3 应用IDE生成代码
默认状况下,IntelliJ或其余ide是不晓得生成的代码的,因而对这些生成符号的援用将被标记为不可解析的。为了让IntelliJ可能对生成的代码进行操作,须要增加如下配置。
build/generated/ksp/main/kotlin/build/generated/ksp/main/java/
当然,也能够是资源目录。
build/generated/ksp/main/resources/
在应用的时候,还须要在KSP processor模块中配置这些生成的目录。
kotlin { sourceSets.main { kotlin.srcDir("build/generated/ksp/main/kotlin") } sourceSets.test { kotlin.srcDir("build/generated/ksp/test/kotlin") }}
如果在Gradle插件中应用IntelliJ IDEA和KSP,那么下面的代码片段会给出以下正告:
Execution optimizations have been disabled for task ':publishPluginJar' to ensure correctness due to the following reasons:
对于这种正告,咱们能够在模块中增加上面的代码。
plugins { // … idea}// …idea { module { // Not using += due to https://github.com/gradle/gradle/issues/8749 sourceDirs = sourceDirs + file("build/generated/ksp/main/kotlin") // or tasks["kspKotlin"].destination testSourceDirs = testSourceDirs + file("build/generated/ksp/test/kotlin") generatedSourceDirs = generatedSourceDirs + file("build/generated/ksp/main/kotlin") + file("build/generated/ksp/test/kotlin") }}
目前,已有不少应用 APT 的三方库减少了对 KSP 的反对,如下。
Library | Status | Tracking issue for KSP |
---|---|---|
Room | Experimentally supported | |
Moshi | Officially supported | |
RxHttp | Officially supported | |
Kotshi | Officially supported | |
Lyricist | Officially supported | |
Lich SavedState | Officially supported | |
gRPC Dekorator | Officially supported | |
Auto Factory | Not yet supported | Link |
Dagger | Not yet supported | Link |
Hilt | Not yet supported | Link |
Glide | Not yet supported | Link |
DeeplinkDispatch | Supported via airbnb/DeepLinkDispatch#323 |