作者:程序员江同学
转载地址:https://juejin.cn/post/7116305314529411085
前言
注解处理器是Android
开发中一种常用的技术,很多常用的框架比如ButterKnife
,ARouter
,Glide
中都使用到了注解处理器相关技术
但是如果项目比较大的话,会很容易发现KAPT
是拖慢编译速度的常见原因,这也是谷歌推出KSP
取代KAPT
的原因
目前KSP
已经发布了正式版,越来越多的框架也已经支持了KSP
,因此现在应该是时候把你的迁移到KSP
了~
本文主要介绍了KSP
的一些优势与原理,以及使用KSP
快速实现一个简易的ButterKnife
框架,以实现KSP
的快速上手
为什么使用KSP
KAPT
为什么慢?
![](https://img.haomeiwen.com/i28055051/d82697716581a199.png)
从上面这张图其实就可以看出原因了,KAPT
处理注解的原理是将代码首先生成Java Stubs
,再将Java Stubs
交给APT
处理的,这样天然多了一步,自然就耗时了
同时在项目中可以发现,往往生成Java Stubs
的时间比APT
真正处理注解的时间要长,因此使用KSP
有时可以得到100%以上的速度提升
同时由于KAPT
不能直接解析Kotlin
的特有的一些符号,比如data class
,当我们要处理这些符号的时候就比较麻烦,而KSP
则可以直接识别Kotlin
符号
KSP
是什么
Kotlin Symbol Processing (KSP) is an API that you can use to develop lightweight compiler plugins. KSP provides a simplified compiler plugin API that leverages the power of Kotlin while keeping the learning curve at a minimum. Compared to kapt, annotation processors that use KSP can run up to 2 times faster.
官网对KSP
的描述如上,主要说了两点:
-
KSP
是对KCP
(Kotlin
编译器插件)的轻量化封装,可以在降低我们学习曲线的同时,可以使用到Kotlin
编译器的一些能力 - 相比于
KAPT
,KSP
处理注解可以得到2倍的性能提升
上面得到了KCP
(Kotlin
编译器插件),KCP
在kotlinc
过程中提供 hook
时机,可以在此期间解析 AST
、修改字节码产物等,Kotlin
的不少语法糖都是 KCP
实现的,例如 data class
、 @Parcelize
、kotlin-android-extension
等, 如今火爆的 Compose
其编译期工作也是借助 KCP
完成的。
KCP
虽然强大,但开发成本也很高,学习曲线比较陡峭,因此当我们只需要处理注解等问题时,使用KCP
是多余的,于是Google
推出了KSP
,它基于KCP
,但屏蔽了KCP
的细节,让我们专注于注解处理的业务
KSP
实战
ButterKnife
是上古时期比较常用的一个框架,现在有KAE
和ViewBinding
了,当然也就用不上了
ButterKnife
的主要原理是为注解解析的字段自动生成findViewById
的代码,其中主要也是用到了注解处理技术,接下来我们就一起实现一个简易的ButterKnife
框架
1. 声明注解
annotation class BindView(val value: Int)
首先要做的就是声明BindView
注解
2. 添加ProcessorProvider
class ButterKnifeProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return ButterKnifeProcessor(environment.codeGenerator, environment.logger)
}
}
ProcessorProvider
用于提供注解处理器,其中主要提供了SymbolProcessorEnvironment
,主要提供了以下功能
-
environment.options
可以获取build.gradle
声明的ksp option
-
environment.logger
提供了logger
供我们打印日志 - 最常用的是
environment.codeGenerator
,用于生成与管理文件,不使用此API
创建的文件将不会参与增量处理或后续编译。
3. 获取注解处理的符号
class ButterKnifeProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(BindView::class.qualifiedName!!)
val ret = symbols.filter { !it.validate() }.toList()
val butterKnifeList = symbols
.filter { it is KSPropertyDeclaration && it.validate() }
.map { it as KSPropertyDeclaration }.toList()
ButterKnifeGenerator().generate(codeGenerator, logger, butterKnifeList)
return ret
}
}
代码其实很简单,找出被BindView
注解的符号,并过滤出KSPropertyDeclaration
,也就是声明的属性
4. 使用kotlin-poet
生成代码
class ButterKnifeGenerator {
@OptIn(KotlinPoetKspPreview::class)
fun generate(
codeGenerator: CodeGenerator, logger: KSPLogger,list: List<KSPropertyDeclaration>
) {
// 将获取的符号按包名与类名分组
val map = list.groupBy {
val parent = it.parent as KSClassDeclaration
val key = "${parent.toClassName().simpleName},${parent.packageName.asString()}"
key
}
map.forEach {
val classItem = it.value[0].parent as KSClassDeclaration
// 添加文件
val fileSpecBuilder = FileSpec.builder(
classItem.packageName.asString(),
"${classItem.toClassName().simpleName}ViewBind"
)
// 添加方法
val functionBuilder = FunSpec.builder("bindView")
.receiver(classItem.toClassName())
it.value.forEach { item ->
// 获取属性名与注解的值
val symbolName = item.simpleName.asString()
val annotationValue =
(item.annotations.firstOrNull()?.arguments?.firstOrNull()?.value as? Int) ?: 0
functionBuilder.addStatement("$symbolName = findViewById(${annotationValue})")
}
// 写文件
fileSpecBuilder.addFunction(functionBuilder.build())
.build()
.writeTo(codeGenerator, false)
}
}
}
代码也不长,主要分为以下几步:
- 因为我们获取的是所有被
BindView
注解的忏悔,因此需要将获取的符号根据包名与类名分组 - 遍历
map
,生成文件,并在其中生成相应Activity
的bindView
扩展方法 - 在
bindView
方法中,利用相关API
获取属性名与注解的值 - 利用
kotlin-poet
与codeGenerator
生成代码
5. 生成的代码
package com.zj.ksp_butterknife
import kotlin.Unit
public fun MainActivity.bindView(): Unit {
fabView = findViewById(2131230915)
toolbar = findViewById(2131231195)
}
在build/generated/ksp/debug/kotlin
目录下可以看到生成的代码,如上所示,其实就是给MainActivity
添加了个扩展方法,在其中会自动为被注解的属性赋值
6. 在项目中使用
plugins {
id("com.google.devtools.ksp")
}
android {
kotlin {
sourceSets {
// 让IDE识别KSP生成的代码
main.kotlin.srcDirs += 'build/generated/ksp'
}
}
}
dependencies {
implementation project(':butterknife-annotation')
ksp project(':butterknife-ksp-compiler')
}
与kapt
使用的步骤其实差不多,主要区别在于默认情况下IDE
并不认识KSP
生成的代码,为了在IDE
中支持引用相关的类,需要扩展main.kotlin.srcDirs
总结
本文主要介绍了KSP
的一些特性以及如何利用KSP
快速实现一个简易的ButterKnife
,KSP
相比KAPT
主要有以下优势
-
KSP
性能更好,有时可以达到2倍的速度提升; -
KSP
开发起来更加方便,不需要自己处理增量编译逻辑; -
KSP
支持多平台,而KAPT
只支持JVM
平台 -
KSP
拥有更符合Kotin
习惯的API
,同时可以识别Kotin
特有的符号
总得来说,KSP
目前已经发布正式版了,越来越多的框架也已经支持了KSP
,因此现在应该是时候把你的应用迁移到KSP
了~
网友评论