##第4講 編譯插樁操縱字節碼
拉勾教育:https://kaiwu.lagou.com/course/courseInfo.htm
這一講的內容對我來說挺新鮮的,編譯插樁只聽過這個詞,並一直認爲是一項高不可及的黑科技,看完這節課,感覺還是沒那麼可怕的。這節課舉了一個例子手把手地說明了怎麼實現編譯插樁。這裏記錄一下思路,具體代碼看課程。
0、需求
先說一下需求,不然說了半天都不知道用這個插樁來幹什麼。
需求:
記錄每一個頁面的打開和關閉事件,並通過各種 DataTracking 的框架上傳到服務器,用來日後做數據分析。
實際演示的需求是在每個activity的onCreate方法中打印一行日誌
面對這樣的需求,一般人都會想到,這其實就是在每一個 Activity 的 onCreate 和 onDestroy 方法中,分別添加頁面打開和頁面關閉的邏輯。常見的做法有以下兩種:
- 修改項目中現有的每一個 Activity,這樣顯然不夠高大上,並且如果項目以後需要添加新的頁面,這套邏輯需要重新拷貝一遍,非常容易遺漏。
- 將項目中所有的 Activity 繼承自 BaseActivity,將頁面打開和關閉的邏輯添加在 BaseActivity中,這種方案看起來比第 1 種方案高級得多,並且後續項目中有新的 Activity,直接繼承 BaseActivity 即可。但是這種方案對第三方依賴庫中的界面則無能爲力,因爲我們沒有第三方依賴庫的源碼。
就是在這種環境下,一種更加優雅更加完整的方案應運而生:編譯插樁。
一、編譯插樁是什麼
編譯插樁就是在代碼編譯期間修改已有的代碼或者生成新代碼。
先回顧一下 Android 項目中 .java 文件的編譯過程:
從上圖可以看出,我們可以在 1、2 兩處對代碼進行改造。
- 在 .java 文件編譯成 .class 文件時,APT、AndroidAnnotation 等就是在此處觸發代碼生成。
- 在 .class 文件進一步優化成 .dex 文件時,也就是直接操作字節碼文件,也是本課時主要介紹的內容。這種方式功能更加強大,應用場景也更多。但是門檻比較高,需要對字節碼有一定的理解
課程主要介紹第 2 種實現方式,過程如下示意圖:
二、插樁工具介紹
目前市面上主要流行兩種實現編譯插樁的方式:
1、AspectJ(我表示沒聽過)
AspectJ 是老牌 AOP(Aspect-Oriented Programming)框架,如果你做過 J2EE 開發可能對這個框架更加熟悉,經常會拿這個框架跟 Spring AOP 進行比較。其主要優勢是成熟穩定,使用者也不需要對字節碼文件有深入的理解。
2、ASM(我僅見過這個詞)
目前另一種編譯插樁的方式 ASM 越來越受到廣大工程師的喜愛。通過 ASM 可以修改現有的字節碼文件,也可以動態生成字節碼文件,並且它是一款完全以字節碼層面來操縱字節碼並分析字節碼的框架。
ASM 是一套開源框架,其中幾個常用的 API 如下:
ClassReader:負責解析 .class 文件中的字節碼,並將所有字節碼傳遞給 ClassWriter。
ClassVisitor:負責訪問 .class 文件中各個元素,還記得上一課時我們介紹的 .class 文件結構嗎?ClassVisitor 就是用來解析這些文件結構的,當解析到某些特定結構時(比如類變量、方法),它會自動調用內部相應的 FieldVisitor 或者 MethodVisitor 的方法,進一步解析或者修改 .class 文件內容。
ClassWriter:繼承自 ClassVisitor,它是生成字節碼的工具類,負責將修改後的字節碼輸出爲 byte 數組。
二、實際思路
1、 將所有的.class文件找出來
怎麼找?
通過自定義gradle插件來實現(具體是通過自定義插件內的 Transform來實現)
什麼是 Transform ?
Transform 可以被看作是 Gradle 在編譯項目時的一個 task,在 .class 文件轉換成 .dex 的流程中會執行這些 task,對所有的 .class 文件(可包括第三方庫的 .class)進行轉換,轉換的邏輯定義在 Transform 的 transform 方法中。實際上平時我們在 build.gradle 中常用的功能都是通過 Transform 實現的,比如混淆(proguard)、分包(multi-dex)、jar 包合併(jarMerge)。
2、從所有class文件中將目標class文件找出來,並在目標class文件中的目標方法內插入目標字節碼
也就是找出activity對應的class文件,並在該文件中找到onCreate方法,然後在方法內插入打印一行日誌的字節碼
這一步通過ASM來實現
具體的代碼怎麼寫就不貼了,課程裏有源碼,個人認爲理清思路比記住源碼更重要,也更容易記住。雖然代碼也很重要,但記憶力是有限的,特別是像咱這種“老年人”,只好選擇性地記一些東西。
由於水平有限,如果文中存在錯誤之處,請大家批評指正,歡迎大家一起來分享、探討!
博客:http://blog.csdn.net/MingHuang2017
GitHub:https://github.com/MingHuang1024
Email: [email protected]
.com/MingHuang1024](https://github.com/MingHuang1024)
Email: [email protected]
微信:724360018