口碑的 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 的依賴關係。算法
幾百個 Bundle,靠人工一個個看,是梳理不過來的。並且,每一個版本都有代碼更新,依賴關係都有可能變化。所以,須要咱們開發對應的工具進行分析。json
咱們知道,在 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
這種方案出現的問題:markdown
爲了解決方案 1 中的問題,咱們使用方案 2 進行依賴分析,就是分析每一個 Bundle 的每一個 Java 文件的 import 區域,而後創建它們之間的映射關係。網絡
以 o2ocommon 這個 Bundle 爲例,bundle、module、class 和 import 的區域關係以下:框架
而後,咱們將 Bundle 之間的依賴,轉換爲分析 Java 文件之間的依賴;而且可以計算出 Bundle 之間有多少個類依賴,以及依賴了多少次。
方案 2 經過
複製代碼
經過腳本,將 Bundle 對應的 gitlab 代碼庫拉取到的本地,切換到須要的分支。
遍歷 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;//依賴關係表
...
}
複製代碼
建立好 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
...
}
複製代碼
查找 build.gradle 中的 src 屬性,找到 Java 代碼的存放位置,獲取 *.java 文件的列表,建立 JavaFile 對象:
public class JavaFile implements Serializable {
private String className;//類全稱
private List<ImportModel> imports;//該類的imports文件
private GradleModule parentModule;//所在的Bundle
...
}
複製代碼
這一步是整個方案的核心,須要解析 Java 的語法,將 Java 文件的 import 區域過濾出來。
將 import 的類文件,在 Java 文件中進行搜索,若是未引用到,則刪除該 import,若是存在,則保留。而後建立 import 對象:
public class ImportModel implements Serializable {
private String className;//該import的包名+類名
private Bundle dependBundle;//該類所在的Bundle
...
}
複製代碼
通過上述遞歸算法,咱們創建了 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
複製代碼
/** * 依賴分析 * 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()));
}
}
}
}
複製代碼
咱們把有相互依賴的 Bundle 進行連線,獲得以下圖:
化成圓形的圖爲:
爲了更加準確地衡量 Bundle 之間的依賴程度,後續咱們能夠將依賴關係轉換成 markdown 表格形式:更具體地展現 Bundle 之間依賴、以及被依賴的狀況,以及被依賴多少次也可以清晰展示。除此以外,咱們甚至能夠知道具體是依賴哪一個類。
1. 上述分析方法有效:
有了上述分析結果,爲咱們後續減少包大小、增刪 Bundle、Bundle 升級提供了強有力的指導,爲後續解除 Bundle 之間的依賴提供了詳細的數據參考;
2. 從依賴表中,咱們也能夠看到哪些 Bundle 是葉子節點,能夠根據是否葉子節點肯定 packageId 的分配。
3. 對於經過反射的方式進行依賴的狀況,目前還比較難統計到:
好比 Class.forName("com.koubei.android.xxx")
之類的,後續能夠考慮其餘方案進行完善。