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

背景

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

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

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

爲了減少包大小,咱們須要肯定這些 Bundle 之間的依賴關係,更確切一點,咱們想知道這些 Bundle 的依賴程度:若是刪除某個 Bundle,將會對剩餘的哪些 Bundle 有影響?有哪些 Bundle 能夠直接刪除?解決這些問題,咱們須要分析這幾百多個 Bundle 的依賴關係。算法

1. Bundle 依賴分析方法

幾百個 Bundle,靠人工一個個看,是梳理不過來的。並且,每一個版本都有代碼更新,依賴關係都有可能變化。所以,須要咱們開發對應的工具進行分析。json

方案 1:分析 build.gradle

咱們知道,在 Android 開發中,若是咱們須要依賴某個 Jar 包,咱們會在 module 的 build.gradle 中添加,好比:api

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 之間的依賴。bash

Bundle反饋

【結論】

這種方案出現的問題:markdown

  • 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
相關文章
相關標籤/搜索