Transform 被廢棄,ASM 如何適配?
theme: smartblue highlight: a11y-dark
前言
Transform API
是 AGP1.5
就引入的特性,主要用於在 Android
構建過程中,在 Class
轉Dex
的過程中修改 Class
位元組碼。利用 Transform API
,我們可以拿到所有參與構建的 Class
檔案,然後可以藉助ASM
等位元組碼編輯工具進行修改,插入自定義邏輯。
國內很多團隊都或多或少的用 AGP
的 Transform API
來搞點兒黑科技,比如無痕埋點,耗時統計,方法替換等。但是在AGP7.0
中Transform
已經被標記為廢棄了,並且將在AGP8.0
中移除。而AGP8.0
應該會在今年內釋出,可以說是已經近在眼前了。所以現在應該是時候瞭解一下,在Transform
被廢棄之後,該怎麼適配了。
Transform Action
介紹
Transform API
是由AGP
提供的,而Transform Action
則是由Gradle
提供。不光是 AGP
需要 Transform
,Java
也需要,所以由 Gradle
來提供統一的 Transform API
也合情合理。
這應該也是Transform API
被廢棄的原因,既然Gradle
已經統一提供了API
,AGP
也就沒必要自定義一套了。
關於 TransformAction
如何使用,Gradle
官方已經提供了很詳細的文件–Transforming dependency artifacts on resolution,具體使用可以直接參考文件
AsmClassVisitorFactory
介紹
直接使用Transform Action
的話還是有些麻煩,跟Transform API
一樣,需要手動處理增量編譯的邏輯。AGP
很貼心的為我們又做了一層封裝,提供了AsmClassVisitorFactory
來方便我們使用Transform Action
進行ASM
操作。
根據官方的說法,AsmClassVisitoFactory
會帶來約18%的效能提升,同時可以減少約5倍程式碼
程式碼實戰
接下來我們利用AGP
的AsmClassVisitorFactory API
,來實現方法執行耗時的插樁。
實現AsmClassVisitorFactory
```kotlin
abstract class TimeCostTransform: AsmClassVisitorFactory
override fun isInstrumentable(classData: ClassData): Boolean {
return true
}
} ```
AsmClassVisitorFactory
即建立ClassVisitor
物件的工廠。此介面的實現必須是一個抽象類,createClassVisitor
返回我們自定義的ClassVisitor
,在自定義Visitor
處理完成後,需要傳內容傳遞給下一個Visitor
,因此我們將其放在建構函式中傳入isInstrumentable
用於控制我們的自定義Visitor
是否需要處理這個類,通過這個方法可以過濾我們不需要的類,加快編譯速度
自定義ClassVisitor
```kotlin
class TimeCostClassVisitor(nextVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM5, nextVisitor) {
override fun visitMethod(
access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array
@Override
override fun onMethodEnter() {
// 方法開始
if (isNeedVisiMethod(name)) {
mv.visitLdcInsn(name);
mv.visitMethodInsn(
INVOKESTATIC, "com/zj/android_asm/TimeCache", "putStartTime","(Ljava/lang/String;)V", false
);
}
super.onMethodEnter();
}
@Override
override fun onMethodExit(opcode: Int) {
// 方法結束
if (isNeedVisiMethod(name)) {
mv.visitLdcInsn(name);
mv.visitMethodInsn(
INVOKESTATIC, "com/zj/android_asm/TimeCache", "putEndTime","(Ljava/lang/String;)V", false
);
}
super.onMethodExit(opcode);
}
}
return newMethodVisitor
}
private fun isNeedVisiMethod(name: String?):Boolean {
return name != "putStartTime" && name != "putEndTime" && name != "<clinit>" && name != "printlnTime" && name != "<init>"
}
} ```
這裡就跟普通的ASM
操作沒什麼不同了,主要是通過ASM
位元組碼插樁,在方法的前後插入如下程式碼,通過計算兩者的時間差來得出方法的耗時
kotlin
fun timeMethod(){
TimeCache.putStartTime("timeMethod") //方法開始插入的程式碼
Thread.sleep(1000)
TimeCache.putEndTime("timeMethod") //方法結束插入的程式碼
}
註冊Transform
老版本的Transform
是註冊在AppExtension
中的,新版本則是註冊在AndroidComponentsExtension
中
kotlin
class TimeCostPlugin : Plugin<Project> {
override fun apply(project: Project) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.onVariants { variant ->
variant.instrumentation.transformClassesWith(TimeCostTransform::class.java,
InstrumentationScope.PROJECT) {}
variant.instrumentation.setAsmFramesComputationMode(
FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
)
}
}
}
- 基於
variant
可實現不同的變種不同的處理邏輯 transformClassesWith
通過InstrumentationScope
控制是否需要掃描依賴庫程式碼setAsmFramesComputationMode
可設定不同的棧幀計算模式,具體可檢視原始碼
AsmClassVisitorFactory
的優勢
通過以上步驟,一個簡單的通過插樁計算方法執行耗時的功能就完成了,這比起老版的Transform API
其實簡化了不少,老版本處理增量更新就需要處理一大堆的邏輯。
可以看到我們這裡並沒有手動處理增量邏輯,這是因為呼叫AsmClassVisitorFactory
的TransformClassesWithAsmTask
繼承自NewIncrementalTask
,已經處理了增量邏輯,不需要我們再手動處理了
同時老版本的Transform
每個Transfrom
各自獨立,如果每個Transform
編譯構建耗時+10s
,各個Transform
疊在一起,編譯耗時就會呈線性增長
而新版本可以看出我們也沒有手動進行IO
操作,這是因為AsmInstrumentationManager
中已經做了統一處理,只需要進行一次IO
操作,然後交給ClassVisitor
連結串列處理,完成後統一交給ClassWriter
寫入
通過這種方式,可以有效地減少IO
操作,這也是新版本API
效能提升的原因
總結
總得來說,由於Transform API
在AGP7.0
已標記為廢棄,並且將在AGP8.0
中移除,是時候瞭解一下如何遷移Transform API
了
而AsmClassVisitorFactory
相比Transform API
,使用起來更加簡單,不需要手動處理增量邏輯,可以專注於位元組碼插樁操作。同時AsmClassVisitorFactory
通過減少IO
的方式,可以得到約20%的效能提升,加快編譯速度。
示例程式碼
本文所有原始碼可見:http://github.com/shenzhen2017/Android-ASM
參考資料
其實 Gradle Transform 就是個紙老虎 —— Gradle 系列(4)
現在準備好告別Transform了嗎? | 擁抱AGP7.0
- 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 如何適配?