Android全埋點解決方案之ASM
Android App 的打包流程,可以參考下圖: 通過下圖可知,我們只要在圖中紅圈處攔截,就可以拿到所有的 .class 檔案,然後遍歷 .class 檔案中的所有方法, 再根據條件找到目標方法,最後進行修改並儲存,就可以插入埋點程式碼了。
Google 從 Android Gradle 1.5.0 開始,提供了 Trans- form API,允許第三方的外掛(Plugin)在 Android App 打包成 .dex 檔案之前的編譯過程中操作 .class 檔案。我們只要實現一套 Transform,去遍歷所有 .class 檔案的所有方法,然後進行修改,最後再對原檔案進行替換,即可達到插入程式碼的目的。
Gradle Transform
Gradle Transform 是 Android 官方提供給開發者在專案構建階段,即由 .class 到 .dex 轉換期間修改 .class 檔案的一套 API。目前比較經典的應用是位元組碼插樁、程式碼注入技術。
概括來說,Transform 就是把輸入的 .class 檔案轉變成目標位元組碼檔案。
我們先了解一下 Transform 的兩個概念:
• TransformInput
TransformInput 是指這些輸入檔案的抽象。它包括兩部分:
1)DirectoryInput 集合
是指以原始碼方式參與專案編譯的所有目錄結構及其目錄下 的原始碼檔案。
2)JarInput 集合
是指以 jar 包方式參與專案編譯的所有本地 jar 包和遠端 jar 包。
• TransformOutputProvider
是指 Transform 的輸出,通過它可以獲取輸出路徑。
我們下面瞭解一下 Transform.java 的定義。 Transform.java 是一個抽象類,它的定義如下: 它定義了幾個抽象方法如下:
Gradle Transform 例項
我們下面實現一個 Gradle Transform 的例項,該例項其實沒有什麼特定功能,僅僅是把所有的輸入檔案原封不動的拷貝到 輸出目錄。 通過 Transform 提供的 API 可以遍歷所有檔案,包括目錄和 jar 包。但是要實現 Transform 的遍歷 .class 檔案的操作,需 要通過 Gradle 外掛來實現。 完整的專案原始碼後續會 release 給大家。
ASM
ASM 是一個功能比較齊全的 Java 位元組碼操作與分析框架。它能被用來動態生成類或者增強既有類的功能。ASM 可以直接 產生二進位制 class 檔案,也可以在類被載入入 Java 虛擬機器之前動態改變類的行為。Java class 被儲存在嚴格格式定義 的 .class 檔案裡,這些類檔案擁有足夠的元資料來解析類中的所有元素,包括類名稱、方法、屬性以及 Java 位元組碼(指令)。 ASM 從類檔案中讀入這些資訊後,能夠改變類行為、分析類的資訊,甚至能夠根據具體的要求生成新的類。 我們下面簡單的介紹一個 ASM 框架中幾個核心的類:
ClassVisitor 定義了一系列的 API,它按照一定的標準次序來遍歷類中的成員。
在 ClassVisitor 中,我們可以根據實際的需求進行條件判斷,只要滿足我們特定條件的類,我們才會去修改它的方法。比如, 我們要自動採集 Button 的點選事件,那麼只有實現了 View$OnClickListener 介面的類,我們才會去遍歷它的方法並找到 onClick(view) 方法,然後進行修改操作。 我們下面重點介紹 ClassVisitor 中的 visit 方法和 visitMethod 方法。
visit 方法
該方法是當掃描類時第一個拜訪的方法。 方法定義如下:
各引數解釋如下:
• version
表示 JDK 的版本,比如 51,代表 JDK 版本 1.7。 各個 JDK 版本對應的數值如下:
• access
類的修飾符。修飾符在 ASM 中是以“ACC_”開頭的常量。可以作用到類級別上 的修飾符有:
• name
類的名稱。通常我們會使用完整的包名 + 類名來表示類,比如:a.b.c.MyClass, 但是在位元組碼中是以路徑的形式表示,即:a/b/c/MyClass。值得注意的是,雖 然是路徑表示法但是不需要寫明類的“.class”副檔名。
• signature
表示泛型資訊,如果類並未定義任何泛型該引數為空。
• superName
表 示 所 繼 承 的 父 類。由 於 Java 的 類 是 單 根 結 構,即 所 有 類 都 繼 承 自 java.lang.Object。因此可以簡單的理解為任何類都會具有一個父類。雖然在編 寫 Java 程式時我們沒有去寫 extends 關鍵字去明確繼承的父類,但是 JDK 在編 譯時總會為我們加上“extends Object”。
• interfaces
表示類實現的介面,在 Java 中,類是可以實現多個不同的介面,因此該引數是 一個數組。
visitMethod 方法
該方法是當掃描器掃描到類的方法時進行呼叫。 方法定義如下:
各引數解釋如下:
• access
表示方法的修飾符。 可以作用到方法級別上的修飾符有:
• name
表示方法名。
• desc
表示方法簽名,方法簽名的格式如下:“( 引數列表 ) 返回值型別”。在 ASM 中不 同的型別對應不同的程式碼:
下面舉幾個方法引數列表對應的方法簽名示例:
• signature
表示泛型相關的資訊。
• exceptions
表示將會丟擲的異常,如果方法不會丟擲異常,該引數為空。
原理概述
我們自定義一個 Gradle Plugin ,可以註冊一個 Transform 物件,然後在 transform 方法裡,分別遍歷目錄和 jar 包,然 後我們就可以遍歷所有的
.class 檔案。然後再利用 ASM 的相關 API,載入相應的
.class 檔案、解析
.class 檔案,就可以找 到滿足一定特定條件的
.class 檔案和相關方法,最後去修改相應的方法以動態插入埋點位元組碼,從而達到自動埋點的效果。
實現步驟
完整的專案原始碼後續會 release 給大家。
缺點
• 暫時沒有什麼發現缺點
知識點
• 位元組碼語法
• Gradle Plugin
• Transform API
• ASM
參考資料
[1] ofollow,noindex" target="_blank">https://www.jianshu.com/p/9039a3e46dbc
[2] http://tools.android.com/tech-docs/new-build-system/transform-api
[3]https://blog.csdn.net/tscyds/article/details/78082861
[4]https://blog.csdn.net/Neacy_Zz/article/details/78546237
[6]https://blog.csdn.net/byeweiyang/article/details/80127789
注:該內容來自神策資料使用者行為洞察研究院出品的《Android 全埋點解決方案》白皮書,檢視完整白皮書可點選 360775e118954518de2c?utm_source=WeChat&utm_medium=free&utm_term=%e9%98%85%e8%af%bb%e5%8e%9f%e6%96%87&utm_content=%e7%99%bd%e7%9a%ae%e4%b9%a6-Android%e5%85%a8%e5%9f%8b%e7%82%b9&utm_campaign=sensorsdata2" rel="nofollow,noindex" target="_blank">《Android 全埋點解決方案》
更多白皮書、報告、乾貨和案例,可以關注“神策資料”和“使用者行為洞察研究院”公眾號瞭解~