編譯插樁操作字節碼(ASM版本)

來源:編譯插樁操縱字節碼,實現不可能完成的任務

編譯插樁

編譯插樁就是在代碼編譯期間修改已有的代碼或者生成新代碼。實際上,我們項目中經常用到的 Dagger、ButterKnife 甚至是 Kotlin 語言,它們都用到了編譯插樁的技術。

Android 項目中 .java 文件的編譯過程

我們可以在 1、2 兩處對代碼進行改造。

  1. 在 .java 文件編譯成 .class 文件時,APT、AndroidAnnotation 等就是在此處觸發代碼生成。

  2. 在 .class 文件進一步優化成 .dex 文件時,也就是直接操作字節碼文件,也是本課時主要介紹的內容。這種方式功能更加強大,應用場景也更多。但是門檻比較高,需要對字節碼有一定的理解。

使用第二種實現方式

編譯插樁實現功能

  • 日志埋點;

  • 性能監控;

  • 動態權限控制;

  • 業務邏輯跳轉時,校驗是否已經登錄;

  • 甚至是代碼調試等。

插樁工具

  • AspectJ
    AspectJ 是老牌 AOP(Aspect-Oriented Programming)框架,如果你做過 J2EE 開發可能對這個框架更加熟悉,經常會拿這個框架跟 Spring AOP 進行比較。其主要優勢是成熟穩定,使用者也不需要對字節碼文件有深入的理解。

  • ASM
    通過 ASM 可以修改現有的字節碼文件,也可以動態生成字節碼文件,并且它是一款完全以字節碼層面來操縱字節碼并分析字節碼的框架

ASM 實現在每一個 Activity 打開時輸出相應的 log 日志

思路

  1. 遍歷項目中所有的 .class 文件
    眾所周知,Android Studio 使用 Gradle 編譯項目中的 .java 文件,并且從 Gradle1.5.0 之后,我們可以自己定義 Transform,來獲取所有 .class 文件引用。但是 Transform 的使用需要依賴 Gradle Plugin。因此我們第一步需要創建一個單獨的 Gradle Plugin,并在 Gradle Plugin 中使用自定義 Transform 找出所有的 .class 文件。

  2. 遍歷到目標 .class 文件 (Activity)之后,通過 ASM 動態注入需要被插入的字節碼
    過濾出目標 Activity 文件,并在目標 Activity 文件的 onCreate 方法中,通過 ASM 插入相應的 log 日志字節碼。

Transform

Transform 可以被看作是 Gradle 在編譯項目時的一個 task,在 .class 文件轉換成 .dex 的流程中會執行這些 task,對所有的 .class 文件(可包括第三方庫的 .class)進行轉換,轉換的邏輯定義在 Transform 的 transform 方法中。

實際上平時我們在 build.gradle 中常用的功能都是通過 Transform 實現的,比如混淆(proguard)、分包(multi-dex)、jar 包合并(jarMerge)。

getName:

設置我們自定義的 Transform 對應的 Task 名稱。Gradle 在編譯的時候,會將這個名稱顯示在控制臺上。比如:Task :app:transformClassesWithXXXForDebug。

getInputType:

在項目中會有各種各樣格式的文件,通過 getInputType 可以設置 LifeCycleTransform 接收的文件類型,此方法返回的類型是 Set<QualifiedContent.ContentType> 集合。

ContentType 有以下 2 種取值。

  • CLASSES:代表只檢索 .class 文件;

  • RESOURCES:代表檢索 java 標準資源文件。

getScopes()

這個方法規定自定義 Transform 檢索的范圍,具體有以下幾種取值:


isIncremental()

表示當前 Transform 是否支持增量編譯,我們不需要增量編譯,所以直接返回 false 即可。

transform()

在 自定義Transform 中最重要的方法就是 transform()。在這個方法中,可以獲取到兩個數據的流向。

  • inputs:inputs 中是傳過來的輸入流,其中有兩種格式,一種是 jar 包格式,一種是 directory(目錄格式)。

  • outputProvider:outputProvider 獲取到輸出目錄,最后將修改的文件復制到輸出目錄,這一步必須做,否則編譯會報錯。

ASM

ASM 是一套開源框架,其中幾個常用的 API 如下:

  • ClassReader:負責解析 .class 文件中的字節碼,并將所有字節碼傳遞給 ClassWriter。

  • ClassVisitor:負責訪問 .class 文件中各個元素,ClassVisitor 就是用來解析這些文件結構的,當解析到某些特定結構時(比如類變量、方法),它會自動調用內部相應的 FieldVisitor 或者 MethodVisitor 的方法,進一步解析或者修改 .class 文件內容。

  • ClassWriter:繼承自 ClassVisitor,它是生成字節碼的工具類,負責將修改后的字節碼輸出為 byte 數組。

具體實現

  1. 創建類 LifeCyclePlugin.groovy 文件。在 LifeCyclePlugin 中重寫 apply 方法,實現插件邏輯
  2. 修改 asm_lifecycle_plugin module 的 build.gradle
  3. 創建 properties 文件
  4. 自定義Transform,實現transform方法
  5. 將自定義的 LifeCycleTransform 注冊到 Gradle 插件中
  6. 使用 ASM,插入字節碼到 Activity 文件
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。