kotlin-android-extensions 外掛將被正式移除,如何無縫遷移?

語言: CN / TW / HK

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 主要有以下優點

  1. 接入方便,不需要手動修改舊程式碼,可以真正做到無縫遷移
  2. 與 KAE 表現一致(都支援 viewId 快取,並在頁面銷燬時清除),不會引入預期外的 bug
  3. 統一遷移,回測方便,如果存在問題時,應該是批量存在的,避免手動修改可能引入線上 bug 的問題
  4. 通過生成原始碼的方式相容 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 外掛與編譯器外掛的原始碼,它們的具體結構如下:

  1. AndroidExtensionsSubpluginIndicatorKAE外掛的入口
  2. AndroidSubplugin用於配置傳遞給編譯器外掛的引數
  3. AndroidCommandLineProcessor用於接收編譯器外掛的引數
  4. AndroidComponentRegistrar用於註冊如圖的各種Extension

關於更細節的分析可以參閱:kotlin-android-extensions 外掛到底是怎麼實現的?

總的來說,其實 KAE 主要做了兩件事

  1. KAE 會將 viewId 轉化為 findViewByIdCached 方法呼叫
  2. KAE 會在頁面關閉時清除 viewId cache

那麼我們要無縫遷移,就也要實現相同的效果

Kace 原理解析

第一次嘗試

我們首先想到的是解析 layout 自動生成擴充套件屬性,如下圖所示

```kotlin // 生成的程式碼 val AndroidExtensions.button1 get() = findViewByIdCached