第4講 編譯插樁操縱字節碼

##第4講 編譯插樁操縱字節碼

拉勾教育:https://kaiwu.lagou.com/course/courseInfo.htm

這一講的內容對我來說挺新鮮的,編譯插樁只聽過這個詞,並一直認爲是一項高不可及的黑科技,看完這節課,感覺還是沒那麼可怕的。這節課舉了一個例子手把手地說明了怎麼實現編譯插樁。這裏記錄一下思路,具體代碼看課程。

0、需求

先說一下需求,不然說了半天都不知道用這個插樁來幹什麼。

需求:

記錄每一個頁面的打開和關閉事件,並通過各種 DataTracking 的框架上傳到服務器,用來日後做數據分析。

實際演示的需求是在每個activity的onCreate方法中打印一行日誌

面對這樣的需求,一般人都會想到,這其實就是在每一個 Activity 的 onCreate 和 onDestroy 方法中,分別添加頁面打開和頁面關閉的邏輯。常見的做法有以下兩種:

  1. 修改項目中現有的每一個 Activity,這樣顯然不夠高大上,並且如果項目以後需要添加新的頁面,這套邏輯需要重新拷貝一遍,非常容易遺漏。
  2. 將項目中所有的 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章