別了 KAPT , 使用 KSP 快速實現 ButterKnife
theme: smartblue highlight: a11y-dark
前言
註解處理器是Android
開發中一種常用的技術,很多常用的框架比如ButterKnife
,ARouter
,Glide
中都使用到了註解處理器相關技術
但是如果項目比較大的話,會很容易發現KAPT
是拖慢編譯速度的常見原因,這也是谷歌推出KSP
取代KAPT
的原因
目前KSP
已經發布了正式版,越來越多的框架也已經支持了KSP
,因此現在應該是時候把你的遷移到KSP
了~
本文主要介紹了KSP
的一些優勢與原理,以及使用KSP
快速實現一個簡易的ButterKnife
框架,以實現KSP
的快速上手
為什麼使用KSP
KAPT
為什麼慢?
從上面這張圖其實就可以看出原因了,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. 聲明註解
kotlin
annotation class BindView(val value: Int)
首先要做的就是聲明BindView
註解
2. 添加ProcessorProvider
kotlin
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. 獲取註解處理的符號
kotlin
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
生成代碼
```kotlin
class ButterKnifeGenerator {
@OptIn(KotlinPoetKspPreview::class)
fun generate(
codeGenerator: CodeGenerator, logger: KSPLogger,list: List
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)
}
}
} ```
代碼也不長,主要分為以下幾步:
1. 因為我們獲取的是所有被BindView
註解的懺悔,因此需要將獲取的符號根據包名與類名分組
2. 遍歷map
,生成文件,並在其中生成相應Activity
的bindView
擴展方法
3. 在bindView
方法中,利用相關API
獲取屬性名與註解的值
4. 利用kotlin-poet
與codeGenerator
生成代碼
5. 生成的代碼
```kotlin 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
了~
示例代碼
本文所有代碼可見:https://github.com/shenzhen2017/ksp-butterknife
參考資料
Kotlin 編譯器插件:我們究竟在期待什麼?
Kotlin Symbol Processors
我正在參與掘金技術社區創作者簽約計劃招募活動,點擊鏈接報名投稿。
- kotlin-android-extensions 插件到底是怎麼實現的?
- 江同學的 2022 年終總結,請查收~
- kotlin-android-extensions 插件將被正式移除,如何無縫遷移?
- 學習一下 nowinandroid 的構建腳本
- Kotlin 默認可見性為 public,是不是一個好的設計?
- 2022年編譯加速的8個實用技巧
- 落地 Kotlin 代碼規範,DeteKt 瞭解一下~
- Gradle 進階(二):如何優化 Task 的性能?
- 開發一個支持跨平台的 Kotlin 編譯器插件
- 開發你的第一個 Kotlin 編譯器插件
- Kotlin 增量編譯是怎麼實現的?
- Gradle 都做了哪些緩存?
- K2 編譯器是什麼?世界第二高峯又是哪座?
- Android 性能優化之 R 文件優化詳解
- Kotlin 快速編譯背後的黑科技,瞭解一下~
- 別了 KAPT , 使用 KSP 快速實現 ButterKnife
- Android Apk 編譯打包流程,瞭解一下~
- 如何優雅地擴展 AGP 插件
- ASM 插樁採集方法入參,出參及耗時信息
- Transform 被廢棄,ASM 如何適配?