背景

口碑的 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 之间的依赖。

【结论】

这种方案出现的问题:

  • dependencies 的依赖可能有冗余,会有多余的 dependencies 出现,会影响结果的准确性;
  • 这种方案我们只能在知道 Bundle 之间的依赖关系,并不知道依赖了其中的多少个类?有哪些地方依赖?

方案 2:分析每个 Bundle 的每个 Java 文件的 import 区域

为了解决方案 1 中的问题,我们使用方案 2 进行依赖分析,就是分析每个 Bundle 的每个 Java 文件的 import 区域,然后建立它们之间的映射关系。

以 o2ocommon 这个 Bundle 为例,bundle、module、class 和 import 的区域关系如下:

然后,我们将 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;//包含的moduleprivate String packageId;private String groupId;//groupIdprivate String artifactId;//artifactIdprivate 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;//隶属那个Bundleprivate 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文件的Listprivate 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文件所在的BundleBundle 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中类,所在的BundleBundle bundle = mJavaFileBundleMap.get(importClassName);if (bundle == null) {//没有查到该类所在的BundleJDALog.info(String.format("%s depend bundle not found.", importModel.getClassName()));} else if (bundle == javaFile.getParentModule().getBundle()) {//内部依赖;该import类和当前类在同一个Bundle中JDALog.info("internal depend.");} else {//currentBundle依赖bundleDependency dependency = dependencyMap.get(bundle);if (dependency == null) {dependency = new Dependency();dependencyMap.put(bundle, dependency);}//将依赖次数+1dependency.setDependCount(dependency.getDependCount() + 1);//能找到对应的Bundle依赖JDALog.info(String.format("%s depend %s", javaFile.getParentModule().getBundle().getName(), bundle.getName()));}}}}
复制代码

3. 依赖结果分析

我们把有相互依赖的 Bundle 进行连线,得到如下图:

化成圆形的图为:

为了更加准确地衡量 Bundle 之间的依赖程度,后续我们可以将依赖关系转换成 markdown 表格形式:更具体地展示 Bundle 之间依赖、以及被依赖的情况,以及被依赖多少次也能够清晰展现。除此之外,我们甚至可以知道具体是依赖哪个类。

4. 总结

1. 上述分析方法有效:

有了上述分析结果,为我们后续减小包大小、增删 Bundle、Bundle 升级提供了强有力的指导,为后续解除 Bundle 之间的依赖提供了详细的数据参考;

2. 从依赖表中,我们也可以看到哪些 Bundle 是叶子节点,可以根据是否叶子节点确定 packageId 的分配。

3. 对于通过反射的方式进行依赖的情况,目前还比较难统计到:

比如 Class.forName("com.koubei.android.xxx") 之类的,后续可以考虑其他方案进行完善。

往期阅读

《开篇 | 模块化与解耦式开发在蚂蚁金服 mPaaS 深度实践探讨》

关注我们公众号,获得第一手 mPaaS 技术实践干货

口碑 App 各 Bundle 之间的依赖分析指南相关推荐

  1. java源码依赖分析_高德APP全链路源码依赖分析工程

    一.背景 高德 App 经过多年的发展,其代码量已达到数百万行级别,支撑了高德地图复杂的业务功能.但与此同时,随着团队的扩张和业务的复杂化,越来越碎片化的代码以及代码之间复杂的依赖关系带来诸多维护性问 ...

  2. 高德JS依赖分析工程及关键原理

    一.背景 高德 App 进行 Bundle 化后,由于业务的复杂性,Bundle 的数量非常多.而这带来了一个新的问题--Bundle 之间的依赖关系错综复杂,需要进行管控,使 Bundle 之间的依 ...

  3. Apple ID,APP ID,Bundle ID,iOS Certificates,iOS Provisioning Profiles各自含义以及之间的关系

    1.Apple ID   Apple ID其实就是开发者账号.只不过要成为开发者,需要去苹果开发者中心激活开发权限.激活开发者账号分为三种,个人,公司和企业.这三种账号各自有些区别,下表是他们主要区别 ...

  4. 编译+构建+链接+运行之间的关系分析

    编译+构建+链接+运行之间的关系分析 源文件+编译+部署+测试+打包 Maven+default: 验证(validate)+编译(compile)+测试(test)+打包(package)+验证(v ...

  5. 小程序 foreach_【第2106期】小程序依赖分析实践

    前言 这种可视化分析图还是很直观的,很有趣.今日早读文章由自然醒授权分享. 正文从这开始~~ 用过 webpack 的同学肯定知道 webpack-bundle-analyzer ,可以用来分析当前项 ...

  6. App渠道推广统计:安卓渠道分析和渠道效果统计方法

    随着移动互联网的飞速发展,移动端入口的碎片化时代已经到来,只有掌握用户的内容渠道,就有了移动端入口的能力.APP想要获取用户,就需要借助渠道的力量.下面介绍一些主流的推广渠道和统计方法. 常见的App ...

  7. mysql查询时间类型c语言处理_资讯类app用户热度及资讯类型分析-Mysql进行数据预处理...

    本文是"资讯类app用户热度及资讯类型分析"一文中,Mysql进行数据预处理的部分.因为篇幅可能比较长,而且摘出来不会过于影响原文分析思路,所以这里单独进行介绍.(本文前四部分与正 ...

  8. Mui --- app与服务器之间的交互原理、mui ajax使用

    1.APP与服务器之间的交互原理app端(客户端)与服务端的交互其实理解起来和容易,客户端想服务器端发送请求,服务器端进行数据运算后返回最终结果.结果可以是多种格式: 1.text 文本格式 2.xm ...

  9. 字节码技术在模块依赖分析中的应用

    背景 近年来,随着手机业务的快速发展,为满足手机端用户诉求和业务功能的迅速增长,移动端的技术架构也从单一的大工程应用,逐步向模块化.组件化方向发展.以高德地图为例,Android 端的代码已突破百万行 ...

最新文章

  1. 快速构建深度学习图像数据集,微软Bing和Google哪个更好用?
  2. 【Eclipse 插件】JD-Eclipse
  3. 使用sublime text2怎样新建文件高速生成HTML头部信息?
  4. AppleWatch开发-AlertController
  5. Orleans例子再进一步
  6. Fail2ban详细教程,解决网站被扫描、CC攻击、ssh暴力破解、防爬虫等问题
  7. 一文抽丝剥茧带你掌握复杂Gremlin查询的调试方法
  8. 【深度优先搜索】计蒜客:踏青
  9. 一、Oracle学习笔记:认识数据库
  10. python爬去微博签到数据_GitHub - fs6/weiboSpider: 新浪微博爬虫,用python爬取新浪微博数据...
  11. 学刘红杰老师博客营销,知如何提高博客访问流量
  12. 机器学习误差计算及评估指标
  13. linux vi面板如何复制一行
  14. MyBatisX插件没有出现蓝色鸟
  15. 外卖优惠券小程序源码,美团外卖,饿了么外卖红包
  16. leetcodeOj:66. Plus One
  17. ZK指纹机二次开发中控F18
  18. python爬小说一本一本爬_【学习笔记】Python爬取某一本小说
  19. 诺基亚N95手机使用技巧
  20. java redis点赞_微信亿级在线点赞系统,用Redis如何实现?

热门文章

  1. Jredis--java使用redis
  2. java 欧拉_基于Java实现欧拉积分法
  3. 乘法口诀表的灵活打印
  4. x86 CPU中逻辑地址到物理地址映射过程
  5. Centos7 测试实际网速/带宽
  6. AjaxPro-ajaxMethod 实例
  7. 愚人节的礼物---浅析
  8. app图标有黑边(无法完全填充)的问题
  9. 日更计划Day7 USACO21FEBsilver T2
  10. vertx instance