口碑 App 各 Bundle 之間的依賴分析指南

螞蟻金服移動開發平臺mPaaS發表於2018-10-16

背景

口碑的 O2O 業務 Bundle,目前需要在支付寶和口碑獨客這兩個 App 中的執行。目前口碑 App 也是使用 mPaaS 框架,一些基礎服務比如 ConfigService,H5 容器,RPC 網路庫,AntUI 庫,Sync,掃碼,Push 等,和支付寶保持一致,並對於不相容的地方進行拉分支單獨改造,對於支援多 App 的 Bundle,直接使用支付寶的基線。

那麼,每次業務在支付寶上發版之後,同步到口碑 App 時,都需要將口碑 App 的基線進行升級。所謂基線升級,就是將支付寶中對應的 Bundle 版本號同步到獨客,將有定製分支的 Bundle 進行程式碼 merge。支付寶 App 有幾百個 Bundle,而口碑 App 的 Bundle 規模也已達到相似規模。

這幾百個 Bundle 中,其中幾十個 Bundle 是口碑 App 有分支定製以及特有的,剩下的 Bundle 直接從支付寶已有體系內進行索引。

為了減小包大小,我們需要確定這些 Bundle 之間的依賴關係,更確切一點,我們想知道這些 Bundle 的依賴程度:如果刪除某個 Bundle,將會對剩餘的哪些 Bundle 有影響?有哪些 Bundle 可以直接刪除?解決這些問題,我們需要分析這幾百多個 Bundle 的依賴關係。

1. Bundle 依賴分析方法

幾百個 Bundle,靠人工一個個看,是梳理不過來的。而且,每個版本都有程式碼更新,依賴關係都有可能變化。因此,需要我們開發對應的工具進行分析。

方案 1:分析 build.gradle

我們知道,在 Android 開發中,如果我們需要依賴某個 Jar 包,我們會在 module 的 build.gradle 中新增,比如:

 dependencies {

        provided 
'com.alipay.android.phone.thirdparty:fastjson-api:1.1.45@jar'

        provided 
'com.alipay.android.phone.thirdparty:wire-api:1.5.3@jar'

        ...

 }
複製程式碼

對於某個 Bundle,我們可以通過分析 Bundle 中的 module,然後解析 build.gradle 檔案,獲取 Bundle 之間的依賴。

Bundle反饋

【結論】

這種方案出現的問題:

  • dependencies 的依賴可能有冗餘,會有多餘的 dependencies 出現,會影響結果的準確性;
  • 這種方案我們只能在知道 Bundle 之間的依賴關係,並不知道依賴了其中的多少個類?有哪些地方依賴?

方案 2:分析每個 Bundle 的每個 Java 檔案的 import 區域

為了解決方案 1 中的問題,我們使用方案 2 進行依賴分析,就是分析每個 Bundle 的每個 Java 檔案的 import 區域,然後建立它們之間的對映關係。

Bundle依賴

以 o2ocommon 這個 Bundle 為例,bundle、module、class 和 import 的區域關係如下:

Bundle關係

然後,我們將 Bundle 之間的依賴,轉換為分析 Java 檔案之間的依賴;並且能夠計算出 Bundle 之間有多少個類依賴,以及依賴了多少次。

【結論】

方案 2 通過
複製程式碼

2. 方案實施:JDA 依賴分析工具開發

2.1 拉取每個 Bundle 對應的原始碼

通過指令碼,將 Bundle 對應的 gitlab 程式碼庫拉取到的本地,切換到需要的分支。

2.2 根據 setting.gradle 建立 Bundle

遍歷 Bundle 目錄,如果查詢到 setting.gradle 檔案,就建立一下 Bundle 物件:

public class Bundle implements Serializable, Comparable<Bundle> {

    private String localPath;
    private String name;//資料夾名稱
    private List<GradleModule> moduleList;//包含的module
    private String packageId;

    private String groupId;//groupId
    private String artifactId;//artifactId

    private Map<Bundle, Dependency> dependencyMap;//依賴關係表
    ...
}
複製程式碼

2.3 根據 build.gradle 建立 module

建立好 bundle 之後,遍歷 bundle 的子目錄,查詢 build.gradle 檔案,然後建立 module 物件:

public class GradleModule implements Serializable{

    private String localPath;
    private String name;//module的名稱
    private Bundle bundle;//隸屬那個Bundle
    private List<JavaFile> javaFileList;//module包含的import
    ...
}
複製程式碼

2.4 查詢 Java 檔案所在的 src 目錄,建立 JavaFile

查詢 build.gradle 中的 src 屬性,找到 Java 程式碼的存放位置,獲取 *.java 檔案的列表,建立 JavaFile 物件:

public class JavaFile implements Serializable {

    private String className;//類全稱
    private List<ImportModel> imports;//該類的imports檔案
    private GradleModule parentModule;//所在的Bundle
    ...
}    
複製程式碼

2.5 解析 Java 檔案

這一步是整個方案的核心,需要解析 Java 的語法,將 Java 檔案的 import 區域過濾出來。

2.6 整理 import 區域,刪除多餘的 import

將 import 的類檔案,在 Java 檔案中進行搜尋,如果未引用到,則刪除該 import,如果存在,則保留。然後建立 import 物件:

public class ImportModel implements Serializable {

    private String className;//該import的包名+類名
    private Bundle dependBundle;//該類所在的Bundle
    ...
}    
複製程式碼

2.7 建立對映關係表 Map

經過上述遞迴演算法,我們建立了 Bundle、Module、JavaFile、ImportModel 之間的樹結構。並且儲存了所有 Java 檔案與其所屬 Bundle 之間的對映關係。

    private Map<String, Bundle> mJavaFileBundleMap = new LinkedHashMap<>();//Java檔案與所屬Bundle之間的對映關係
    private List<JavaFile> mAllJavaFile = new ArrayList<>();//所有Java檔案的List
    private List<Bundle> mBundleList = new ArrayList<>();//所有Bundle的List
複製程式碼

3.8 依賴分析

   /**
     * 依賴分析
     * mochuan.zhb@alibaba-inc.com
     */
    private void dependenciesAnalysis() {
        for (JavaFile javaFile : mAllJavaFile) {//遍歷所有的Java檔案
            //獲取Java檔案的Import區域列表
            List<ImportModel> importModelList = javaFile.getImports();
            //獲取當前Java檔案所在的Bundle
            Bundle currentBundle = javaFile.getParentModule().getBundle();
            //獲取當前Bundle與其他Bundle依賴對映表
            Map<Bundle, Dependency> dependencyMap = currentBundle.getDependencyMap();
            if (dependencyMap == null) {
                dependencyMap = new HashMap<>();
                currentBundle.setDependencyMap(dependencyMap);
            }
            if (importModelList == null || importModelList.size() == 0) {
                continue;
            }
            //遍歷Import列表
            for (ImportModel importModel : importModelList) {
                String importClassName = importModel.getClassName();
                if (isClassInWhiteList(importClassName)) {
                    continue;
                }
                //查詢import中類,所在的Bundle
                Bundle bundle = mJavaFileBundleMap.get(importClassName);
                if (bundle == null) {
                    //沒有查到該類所在的Bundle
                    JDALog.info(String.format("%s depend bundle not found.", importModel.getClassName()));
                } else if (bundle == javaFile.getParentModule().getBundle()) {
                    //內部依賴;該import類和當前類在同一個Bundle中
                    JDALog.info("internal depend.");
                } else {
                    //currentBundle依賴bundle
                    Dependency dependency = dependencyMap.get(bundle);
                    if (dependency == null) {
                        dependency = new Dependency();
                        dependencyMap.put(bundle, dependency);
                    }
                    //將依賴次數+1
                    dependency.setDependCount(dependency.getDependCount() + 1);
                    //能找到對應的Bundle依賴
                    JDALog.info(String.format("%s depend %s", javaFile.getParentModule().getBundle().getName(), bundle.getName()));
                }
            }
        }
    }
複製程式碼

3. 依賴結果分析

我們把有相互依賴的 Bundle 進行連線,得到如下圖:

Bundle分析結果

化成圓形的圖為:

Bundle分析結果

為了更加準確地衡量 Bundle 之間的依賴程度,後續我們可以將依賴關係轉換成 markdown 表格形式:更具體地展示 Bundle 之間依賴、以及被依賴的情況,以及被依賴多少次也能夠清晰展現。除此之外,我們甚至可以知道具體是依賴哪個類。

4. 總結

1. 上述分析方法有效:

有了上述分析結果,為我們後續減小包大小、增刪 Bundle、Bundle 升級提供了強有力的指導,為後續解除 Bundle 之間的依賴提供了詳細的資料參考;

2. 從依賴表中,我們也可以看到哪些 Bundle 是葉子節點,可以根據是否葉子節點確定 packageId 的分配。

3. 對於通過反射的方式進行依賴的情況,目前還比較難統計到:

比如 Class.forName("com.koubei.android.xxx") 之類的,後續可以考慮其他方案進行完善。

往期閱讀

《開篇 | 模組化與解耦式開發在螞蟻金服 mPaaS 深度實踐探討》

關注我們公眾號,獲得第一手 mPaaS 技術實踐乾貨

QRCode

相關文章