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 無縫遷移的框架
目前已經開源,開源地址可見:https://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