口碑的 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")
之类的,后续能够考虑其余方案进行完善。