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 無縫遷移的框架

目前已經開源,開源地址可見:http://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