口碑 App 各 Bundle 之间的依赖分析指南

口碑 App 各 Bundle 之间的依赖分析指南口碑的 O2O 业务 Bundle,目前需要在支付宝和口碑独客这两个 App 中的运行。目前口碑 App 也是使用 mPaaS 框架,一些基础服务比如 ConfigService,H5 容器,RPC 网络库,AntUI 库,Sync,扫码,Push 等,和支付宝保持一致,并对于…

背景

口碑的 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

今天的文章口碑 App 各 Bundle 之间的依赖分析指南分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20290.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注