腾讯|5年磨一剑|优酷Android包瘦身治理思路全解( 六 )



现在 , 如果让你来回答以下几个问题 , 是不是就可以信手拈来 , 轻松惬意?
apk为什么这么大 , 不同模块/业务/团队 , 分别贡献了多少? 每个团队/业务/模块 , 有哪些可以瘦身的地方 , 进行删除、优化、改造后 , apk能减少多少? 在日常迭代中 , 当前版本相对于上个版本 , 每个团队/业务/模块增加(减少)大小是多少? 实际上 , 优酷自研包大小分析工具Franky , 几乎完全实现了上述拆解后的需求 。 只有一点尚未做到:apk中元素 , 目前还没有做到100%找到模块归属 , 在优酷apk中的分析覆盖率是99.8% ~ 99.9%(apk 100MB ~ 65MB) 。
3.1.1 整体架构
Franky主要由两部分组成:用于application工程的gradle plugin , 以及命令行(cli)分析工具 。 此外 , 还额外依赖(非必需)两个外部数据:模块图谱数据 , 用于将模块大小 , 向上聚合为功能/业务/团队的大小;代码覆盖数据 , 形成可瘦身项分析中的「代码 - 无用类」(SlimLady:类级别不插桩线上代码覆盖度统计框架[1
) 。 整体架构如下图所示:

franky-plugin的作用 , 是在apk构建过程中收集apk所有组成模块 , 以及模块中包含的各类元素 , 此外还包含类混淆映射关系、无用资源分析结果 。 这个分析结果数据与apk文件 , 共同构成了命令行工具franky(cli)的基础(必需)输入文件 。 接下来 , 执行cli命令进行最终的包大小分析 ,产出具体的分析报告 。
纵观这套方案 , 可能会有一个疑问 , 为什么要包含一个构建插件 , 如果能通过一个命令行(cli)工具直接对apk进行分析 , 使用更简单还能更具通用性 , 不是更好吗?这里面有一个非常关键的点在于 , 只有在apk构建过程中 , 才能够获取apk由哪些模块组成、每个模块又包含哪些元素 , 在apk构建完成后的apk文件中 , 这些信息已经丢失 , 所以构建插件必不可少 。 那如果是这样 , 为什么不把命令行工具的所有功能 , 都放在这个构建插件中来实现呢?这是一个好问题 , 目前的考虑是这样的:尽量将构建插件做的比较“薄” , 这样可以减少构建耗时 , 而将主要分析功能放在独立工具 , 可以独立快速迭代 , 而不用频繁在app工程中升级plugin版本 。
3.1.2 关键技术
分析工具看起来简单 , 但是为了获取真实大小 , 以及能够将apk中元素100%进行模块归属 , 在开发过程中还是会遇到不少棘手问题 。
首先 , 应用于构建过程的plugin如何保障兼容性 , 并不是一个简单的问题 。 很多Android Gradle Plugin开发者不太重视兼容性 , 认为针对特定工程实现相关功能就万事大吉 , 这里不深入讨论此话题 , 直接给出franky-plugin考虑并实现的构建环境兼容性 , 或许更能够对这个问题获得直观的认知:

接下来的核心困难是 , 参与apk构建的原始元素 , 与最终apk元素之间 , 存在转换、新增、删除、不变这四种“变化”情况:

上图给出的是基本“变化”情况 , 还有一些特殊情况也需要考虑 , 例如java资源可以“伪装”为其它类型元素、Android资源包含api level大于等于22可用的android:xxx属性 , 且资源的api配置限定符小于22 , 导致生成“-v22”资源文件、AAPT内嵌资源生成独立资源文件等 。 对于删除和不变的元素 , 处理起来比较简单 , 转换和新增的处理则相对复杂一些:
新增 。 新增元素最大的问题在于“找到归属” , 例如有些java8语法在脱糖后生成新的类 。 目前franky中基于生成类名称规则的方式 , 解决了lambda 表达式、默认和静态接口方法的归属情况 , 但是对于方法引用的生成类则暂时无法找到归属(后面计划通过代码Pattern分析来找归属) 。转换 。 发生转换变化的元素 , 如果还进行了“合并”处理(例如dex、resources.arsc) , 此时就需要将元素“拆”出来 , 核心原则是:拆出来后元素大小之和 , 等于拆之前文件大小 。 例如 , 将class从dex文件拆出来后 , 大小相加必需等于dex文件大小 。 这个事情的难度来自于两个方面: 各种字符串、类型共享池 , 需要进行按比例(PSS)分担计算 。各种二进制结构数据的Header、Padding , 需要进行精准计算和归属 。除了这些变化 , 还有一个细节也要考虑:apk中各文件的真实大小占用如何计算?apk本质是一个zip压缩文件 , 其中每个文件均为一个zip entry , zip entry占用大小相加小于apk总大小 , 因此需要将用于记录每条zip entry的额外大小加进来(Local File Header、Central Directory Record) , 同时将共享部分进行按比例分担 。 以优酷为例 , apk大小为65MB , zip entry压缩大小相加是63MB(97%) , 如果不计算额外数据大小 , 仅在计算apk中元素大小时 , 就已经损失了2MB的真实大小!