theme: smartblue
highlight: a11y-dark
前言
kotlin-android-extensions 插件早在 2020 年就已經被宣佈廢棄了,並且將在 Kotlin 1.8 中被正式移除:Discontinuing Kotlin synthetics for views
如上圖示,移除 kotlin-android-extensions 的代碼已經被 Merge 了,因此如果我們需要升級到 Kotlin 1.8,則必須要移除 KAE
那麼移除 kotlin-android-extensions 後,我們該如何遷移呢?
遷移方案
官方的遷移方案如上所示,官方建議我們老項目遷移到 ViewBinding,新項目直接遷移到 Jetpack Compose
對於新代碼我們當然可以這麼做,但是對於大量存量代碼,我們該如何遷移?由於 KAE 簡單易用的特性,它在項目中經常被大量使用,要遷移如此多的存量代碼,並不是一個簡單的工作
存量代碼遷移方案
KAE 存量代碼主要有如圖3種遷移方式
最簡單也最直接的當然就是直接手動修改,這種方式的問題在於要遷移的代碼數量龐大,遷移成本高。同時手動遷移容易出錯,也不容易回測,測試不能覆蓋到所有的頁面,導致引入線上 bug
第二個方案,是把 KAE 直接從 Kotlin 源碼中抽取出來單獨維護,但是 KAE 中也大量依賴了 Kotlin 的源碼,抽取成本較高。同時 KAE 中大量使用了 Kotlin 編譯器插件的 API,而這部分 API 並沒有穩定,當 K2 編譯器正式發佈的時候很可能還會有較大的改動,而這也帶來較高的維護成本。
第三個方案就是本篇要重點介紹的 Kace
Kace 是什麼?
Kace 即 kotlin-android-compatible-extensions,一個用於幫助從 kotlin-android-extensions 無縫遷移的框架
目前已經開源,開源地址可見:http://github.com/kanyun-inc/Kace
相比其它方案,Kace 主要有以下優點
- 接入方便,不需要手動修改舊代碼,可以真正做到無縫遷移
- 與 KAE 表現一致(都支持 viewId 緩存,並在頁面銷燬時清除),不會引入預期外的 bug
- 統一遷移,回測方便,如果存在問題時,應該是批量存在的,避免手動修改可能引入線上 bug 的問題
- 通過生成源碼的方式兼容 KAE,維護成本低
快速遷移
使用 Kace 完成遷移主要分為以下幾步
1. 添加插件到 classpath
```kotlin
// 方式 1
// 傳統方式,在根目錄的 build.gradle.kts 中添加以下代碼
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("com.kanyun.kace:kace-gradle-plugin:1.0.0")
}
}
// 方式 2
// 引用插件新方式,在 settings.gradle.kts 中添加以下代碼
pluginManagement {
repositories {
mavenCentral()
}
plugins {
id("com.kanyun.kace") version "1.0.0" apply false
}
}
```
2. 應用插件
移除kotlin-android-extensions
插件,並添加以下代碼
kotlin
plugins {
id("com.kanyun.kace")
id("kotlin-parcelize") // 可選,當使用了`@Parcelize`註解時需要添加
}
3. 配置插件(可選)
默認情況下 Kace 會解析模塊內的每個 layout 並生成代碼,用户也可以自定義需要解析的 layout
kotlin
kace {
whiteList = listOf() // 當 whiteList 不為空時,只有 whiteList 中的 layout 才會被解析
blackList = listOf("activity_main.xml") // 當 blackList 不為空時,blackList 中的 layout 不會被解析
}
經過以上幾步,遷移就完全啦~
支持的類型
如上所示,Kace 目前支持了以上四種最常用的類型,其他 kotlin-android-extensions 支持的類型如 android.app.Fragment, android.app.Dialog, kotlinx.android.extensions.LayoutContainer 等,由於被廢棄或者使用較少,Kace 目前沒有做支持
版本兼容
| | Kotlin | AGP | Gradle |
|--------|--------|-------|--------|
| 最低支持版本 | 1.7.0 | 4.2.0 | 6.7.1 |
由於 Kace 的目標是幫助開發者更方便地遷移到 Kotlin 1.8,因此 Kotlin 最低支持版本比較高
原理解析:前置知識
編譯器插件是什麼?
Kotlin 的編譯過程,簡單來説就是將 Kotlin 源代碼編譯成目標產物的過程,具體步驟如下圖所示:
Kotlin 編譯器插件,通過利用編譯過程中提供的各種Hook時機,讓我們可以在編譯過程中插入自己的邏輯,以達到修改編譯產物的目的。比如我們可以通過 IrGenerationExtension 來修改 IR 的生成,可以通過 ClassBuilderInterceptorExtension 修改字節碼生成邏輯
Kotlin 編譯器插件可以分為 Gradle 插件,編譯器插件,IDE 插件三部分,如下圖所示
kotlin-android-extensions 是怎麼實現的
我們知道,KAE 是一個 Kotlin 編譯器插件,當然也可以分為 Gradle 插件,編譯器插件,IDE 插件三部分。我們這裏只分析 Gradle 插件與編譯器插件的源碼,它們的具體結構如下:
AndroidExtensionsSubpluginIndicator
是KAE
插件的入口
AndroidSubplugin
用於配置傳遞給編譯器插件的參數
AndroidCommandLineProcessor
用於接收編譯器插件的參數
AndroidComponentRegistrar
用於註冊如圖的各種Extension
關於更細節的分析可以參閲:kotlin-android-extensions 插件到底是怎麼實現的?
總的來説,其實 KAE 主要做了兩件事
- KAE 會將 viewId 轉化為 findViewByIdCached 方法調用
- KAE 會在頁面關閉時清除 viewId cache
那麼我們要無縫遷移,就也要實現相同的效果
Kace 原理解析
第一次嘗試
我們首先想到的是解析 layout 自動生成擴展屬性,如下圖所示
```kotlin
// 生成的代碼
val AndroidExtensions.button1
get() = findViewByIdCached