字節碼技術在模塊依賴分析中的應用

背景

近年來,隨着手機業務的快速發展,爲知足手機端用戶訴求和業務功能的迅速增加,移動端的技術架構也從單一的大工程應用,逐步向模塊化、組件化方向發展。以高德地圖爲例,Android 端的代碼已突破百萬行級別,超過100個模塊參與最終構建。java

試想一下,若是沒有一套標準的依賴檢測和監控工具,用不了多久,模塊的依賴關係就可能會亂成一鍋粥。bash

從模塊 Owner 的角度看,爲何依賴分析這麼重要?網絡

  • 做爲模塊 Owner,我首先想知道「誰依賴了我?依賴了哪些接口」。惟有如此才能評估本模塊改動的影響範圍,以及暴露的接口的合理性。架構

  • 我還想知道「我依賴了誰?調用了哪些外部接口」,對所須要的外部能力作到心中有數。ide

從全局視角看,一個健康的依賴結構,要防止「下層模塊」直接依賴「上層模塊」,更要杜絕循環依賴。經過分析全局的依賴關係,能夠快速定位不合理的依賴,提早暴露業務問題。模塊化

所以,依賴分析是研發過程當中很是重要的一環。函數

常見的依賴分析方式

提到 Android 依賴分析,首先浮如今腦海中的多是如下這些方案:工具

  • 分析 Gradle 依賴樹。組件化

  • 掃描代碼中的 import 聲明。性能

  • 使用 Android Studio 自帶的分析功能。

咱們逐個來分析這幾個方案:

1. Gradle 依賴樹

使用 ./gradlew :<module>:dependencies --configuration releaseCompileClasspath -q 命令,很容易就能夠獲得模塊的依賴樹,如圖:

不難發現,這種方式有兩個問題:

  • 聲明即依賴,即便代碼中沒有使用的庫,也會輸出到結果中。

  • 只能分析到模塊級別,沒法精確到方法級別。

2. 掃描 import 聲明

掃描 Java 文件中的 import 語句,能夠獲得文件(類)之間的調用關係。

由於模塊與文件(類)的對應關係很是容易獲得(掃描目錄)。因此,獲得了文件(類)之間的依賴關係,便是獲得了模塊之間文件(類)級別的依賴關係。

這個方案相比 Gradle 依賴掃描提高告終果維度,能夠分析到文件(類)級別。可是它也存在一些缺點:

  • 沒法處理 import * 的狀況。

  • 掃描「有 import 但未使用對應類」的場景效率過低(須要作源碼字符串查找)。

3. 使用 IDE 自帶的分析功能

觸發 Android Studio 菜單 「Analyze」 -> 「Analyze Dependencies」,能夠獲得模塊間方法級別的依賴關係數據。如圖:

Android Studio 能準確分析到模塊之間「方法級別」的引用關係,支持在 IDE 中跳轉查看,也能掃描到對 Android SDK 的引用。

這個方案比前面兩個都優秀,主要是準確。可是它也有幾個問題:

  • 耗時較長:全面分析 AMap 全源碼,大約須要 10 分鐘。

  • 分析結果沒法爲第三方複用,沒法生成可視化的依賴關係圖。

  • 分析正向依賴和逆向依賴,須要掃描兩次。

總結一下上述三種方案:

  • Gralde 依賴基於工程配置,粒度太粗且結果不許。
  • 「Import - 掃描方案」能拿到文件級別依賴但數據不全。
  • IDE 掃描雖然結果精準,可是數據複用困難,不便於工程化。

爲何要使用字節碼來分析?

參考 Android 構建流程圖,全部的 Java 源代碼和 aapt 生成的 R.java 文件,都會被編譯成 .class 文件,再被編譯爲 dex 文件,最終經過 apkbuilder 生成到 apk 文件中。圖中的 .class 文件便是咱們所說的 Java 字節碼,它是對 Java 源碼的二進制轉義。

在 Android 端,常見的字節碼應用場景包括:

  • 字節碼插樁:用於實現對 UI 、內存、網絡等模塊的性能監控。

  • 修改 jar 包:針對無源碼的庫,經過編輯字節碼來實現一些簡單的邏輯修改。

回到本文的主題,爲何要分析字節碼,而不是 Java 代碼或者 dex 文件?

不使用 Java 代碼是由於有些庫以 jar 或者 aar 的方式提供,咱們獲取不到源碼。不使用 dex 文件是由於它沒有好用的語法分析工具。因此解析字節碼幾乎是咱們惟一的選擇。

如何使用字節碼分析依賴關係?

要獲得模塊之間的依賴關係,其實就是要獲得「模塊間類與類」之間的依賴關係。而要肯定類之間的關係,分析類字節碼的語句便可。

1. 在什麼時機來分析?

瞭解 Android 構建流程的同窗,應該對 transform 這個任務不陌生。它是 Android Gradle 插件提供的一個字節碼 Hook 入口。

在 transform 這個任務中,全部的字節碼文件(包括三方庫) 以 Input 的格式輸入。

以JarInput 爲例,分析其 file 字段,可獲得模塊的名稱。解析 file 文件,便可獲得此模塊全部的字節碼文件。

有了模塊名稱和對應路徑下的 class 文件,就創建了模塊與類的對應關係,這是咱們拿到的第一個關鍵數據。

2. 使用什麼工具分析?

解析 Java 字節碼的工具,最經常使用的包括 Javassit,ASM,CGLib。ASM 是一個輕量級的類庫,性能較好,但須要直接操做 JVM 指令。CGLib 是對 ASM 的封裝,提供了更高級的接口。

相比而言,Javassist 要簡單的多,它基於 Java 的 API ,無需操做 JVM 指令,但其性能要差一些(由於 Javassit 增長了一層抽象)。在工程原型階段,爲了快速驗證結果,咱們優先選擇了 Javassit 。

3. 具體方案是怎樣的?

先看一個簡單的示例,如何分析下面這段代碼的調用關係:

1: package com.account;
2: import com.account.B;
3: public class A {
4:     void methodA() {
5:         B b = new B(); // 初始化了 Class B 的實例 b
6:         b.methodB();   // 調用了 b 的 methodB 方法
7:     }
8: }
複製代碼

第1步:初始化環境,加載字節碼 A.class,註冊語句分析器。

// 初始化 ClassPool,將字節碼文件目錄註冊到 Pool 中。
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath('<class文件所在目錄>')
// 加載類A
CtClass cls = pool.get("com.account.A");
// 註冊表達式分析器到類A
MyExprEditor editor = new MyExprEditor(ctCls)
ctCls.instrument(editor)
複製代碼

第2步:自定義表達式解析器,分析類A(以解析語句調用爲例)。

class MyExprEditor extends ExprEditor {
    @Override
    void edit(MethodCall m) {
        // 語句所在類的名稱
        def clsAName = ctCls.name
        // 語句在哪一個方法被調用
        def where = m.where().methodInfo.getName()
        // 語句在哪一行被調用
        def line = m.lineNumber
        // 被調用類的名稱
        def clsBName = m.className
        // 被調用的方法
        def methodBName = m.methodName
    }
    // 省略其它解析函數 ...
}
複製代碼

ExprEditor 的 edit(MethodCall m) 回調能攔截 Class A 中全部的方法調用(MethodCall)。

除了本例中對 MethodCall 的解析,它還支持解析 new,new Array,ConstructorCall,FieldAccess,InstanceOf,強制類型轉換,try-catch 語句。

解析完 Class A,咱們獲得了 A 對 B 的依賴信息 :


Class1 Class2 Expr method1 method2 lineNo
com.account.A com.account.B NewExpr methodA 5
com.account.A com.account.B methodCall methodA methodB 6

簡單解釋以下:

類 com.account.A 的第5行(methodA方法內),調用了 com.account.B 的構造函數;

類 com.account.A 的第6行(methodA方法內),調用了 com.account.B 的 methodB 函數;

這即是「類和類之間方法級」的依賴數據。結合第1步獲得的「模塊和類」的對應關係,最終咱們便得到了「模塊間方法級的依賴數據」。

基於這些基礎數據,咱們還能夠自定義依賴檢測規則、生成全局的模塊依賴關係圖等,本文就不展開了。

小結

本文主要介紹了模塊依賴分析在研發過程當中的重要性,分析了 Android 常見的依賴分析方案,從 Gradle 依賴樹分析, Import 掃描,使用 IDE 分析,到最後的字節碼解析,方案逐步遞進。越是接近源頭的解法,纔是越根本的解法。

關注高德技術,找到更多出行技術領域專業內容
相關文章
相關標籤/搜索