Transform 被廢棄,ASM 如何適配?

語言: CN / TW / HK

theme: smartblue highlight: a11y-dark


前言

Transform APIAGP1.5 就引入的特性,主要用於在 Android 構建過程中,在 ClassDex的過程中修改 Class 位元組碼。利用 Transform API,我們可以拿到所有參與構建的 Class 檔案,然後可以藉助ASM 等位元組碼編輯工具進行修改,插入自定義邏輯。

國內很多團隊都或多或少的用 AGPTransform API 來搞點兒黑科技,比如無痕埋點,耗時統計,方法替換等。但是在AGP7.0Transform已經被標記為廢棄了,並且將在AGP8.0中移除。而AGP8.0應該會在今年內釋出,可以說是已經近在眼前了。所以現在應該是時候瞭解一下,在Transform被廢棄之後,該怎麼適配了。

Transform Action介紹

Transform API是由AGP提供的,而Transform Action則是由Gradle提供。不光是 AGP 需要 TransformJava 也需要,所以由 Gradle 來提供統一的 Transform API 也合情合理。
這應該也是Transform API被廢棄的原因,既然Gradle已經統一提供了APIAGP也就沒必要自定義一套了。

關於 TransformAction 如何使用,Gradle 官方已經提供了很詳細的文件–Transforming dependency artifacts on resolution,具體使用可以直接參考文件

AsmClassVisitorFactory介紹

直接使用Transform Action的話還是有些麻煩,跟Transform API一樣,需要手動處理增量編譯的邏輯。AGP很貼心的為我們又做了一層封裝,提供了AsmClassVisitorFactory來方便我們使用Transform Action進行ASM操作。 根據官方的說法,AsmClassVisitoFactory會帶來約18%的效能提升,同時可以減少約5倍程式碼

程式碼實戰

接下來我們利用AGPAsmClassVisitorFactory API,來實現方法執行耗時的插樁。

實現AsmClassVisitorFactory

```kotlin abstract class TimeCostTransform: AsmClassVisitorFactory { override fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor { return TimeCostClassVisitor(nextClassVisitor) }

override fun isInstrumentable(classData: ClassData): Boolean {
    return true
}

} ```

  1. AsmClassVisitorFactory即建立ClassVisitor物件的工廠。此介面的實現必須是一個抽象類,
  2. createClassVisitor返回我們自定義的ClassVisitor,在自定義Visitor處理完成後,需要傳內容傳遞給下一個Visitor,因此我們將其放在建構函式中傳入
  3. 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? ): MethodVisitor { val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) val newMethodVisitor = object : AdviceAdapter(Opcodes.ASM5, methodVisitor, access, name, descriptor) {

            @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 ) } } }

  1. 基於variant可實現不同的變種不同的處理邏輯
  2. transformClassesWith通過InstrumentationScope控制是否需要掃描依賴庫程式碼
  3. setAsmFramesComputationMode可設定不同的棧幀計算模式,具體可檢視原始碼

AsmClassVisitorFactory的優勢

通過以上步驟,一個簡單的通過插樁計算方法執行耗時的功能就完成了,這比起老版的Transform API其實簡化了不少,老版本處理增量更新就需要處理一大堆的邏輯。
可以看到我們這裡並沒有手動處理增量邏輯,這是因為呼叫AsmClassVisitorFactoryTransformClassesWithAsmTask繼承自NewIncrementalTask,已經處理了增量邏輯,不需要我們再手動處理了

同時老版本的Transform每個Transfrom各自獨立,如果每個Transform編譯構建耗時+10s,各個Transform疊在一起,編譯耗時就會呈線性增長
而新版本可以看出我們也沒有手動進行IO操作,這是因為AsmInstrumentationManager中已經做了統一處理,只需要進行一次IO操作,然後交給ClassVisitor連結串列處理,完成後統一交給ClassWriter寫入
通過這種方式,可以有效地減少IO操作,這也是新版本API效能提升的原因

總結

總得來說,由於Transform APIAGP7.0已標記為廢棄,並且將在AGP8.0中移除,是時候瞭解一下如何遷移Transform API

AsmClassVisitorFactory相比Transform API,使用起來更加簡單,不需要手動處理增量邏輯,可以專注於位元組碼插樁操作。同時AsmClassVisitorFactory通過減少IO的方式,可以得到約20%的效能提升,加快編譯速度。

示例程式碼

本文所有原始碼可見:http://github.com/shenzhen2017/Android-ASM

參考資料

其實 Gradle Transform 就是個紙老虎 —— Gradle 系列(4)
現在準備好告別Transform了嗎? | 擁抱AGP7.0