由专项式到常态化,首先要做的转变是将关注点从“事”转移到“人”:每一个Byte都是由人(开发者)添加的,对产生的原因(技术、流程、心理等)进行全面分析,并给出有效解决方案,才能够实现专项式到常态化的跃变。这个解决方案,主要包括技术支撑、治理策略两方面,二者相辅相成缺一不可。
2.2 技术支撑
整个技术支撑体系的核心是包大小精准分析,即对apk内任意实体元素(类、资源、so等)获取其在apk文件中实际占用值。因为编译过程会进行各种合并、裁剪、优化、格式转换等,同时apk中不同类型元素的压缩率也不相同,如果使用原始大小作为度量标准,会在包大小治理的整个链路引入较大误差,导致难以抓住瘦身重点,也无法提前精准预判瘦身效果,这对常态治理过程具有非常大的负面影响。
第一点可瘦身项,是指类、资源、so等元素中的“不合理”项,对其进行改造或优化就可以降低包大小。这些可瘦身项细碎并且不易被人工发现,但是累积起来却不容小觑,通过工具化的分析能力可以快速找出这些可瘦身项,一方面提供瘦身指导:为逐步降低包大小提供更多“方向”和“空间”,另一方面用来评判:一个功能/业务/团队,在这段时间内减少/增加了哪些可瘦身项,当前是否已经瘦无可瘦。
第二点归属聚合,是指将apk大小拆解到有效的责任实体,根据app涉及到的研发团队和迭代模式不同,责任实体可以是组织结构中具体的团队,也可以是业务/功能/模块负责人。总之,拆解的目的是明确责任,即团队/业务/功能/模块对apk大小的“贡献”分别是多少。
第三点研发流程,是代码上线的“必经之路”,在这个过程中需要具备及时的增量感知,以及超限后的拦截阻断能力。只有这样才能实现低(人力)成本持续管控,另外这也是去中心化策略的重要技术支撑,可以避免很多低效的增量定位排查、沟通、跟进等工作。
第四点辅助工具,是为研发同学对代码进行瘦身提供一系列切实有效的工具,用来提高效率以及降低风险。目前在优酷已经沉淀了引用分析、代码归属/热度分析、模块下载/版本号同步&查询&对比、progaurd对比(mapping、usage)分析、apk信息查询/对比/反编译等共计十几项工具。
上述技术支撑体系在具体实现上,主要由“分析工具”和“包大小卡口能力“承载,后面会做具体介绍。
2.3 治理策略
常态化治理模式下,治理策略的核心是去中心化。尤其是对于多团队参与研发的app,对每个功能最熟悉的人一定是日常直接负责的(团队)同学,因此最高效的方式其实正是“各家自扫门前雪”的分布式治理模式。其中阈值划分,是实现分布式治理的第一步,即圈定“每家所负责的范围,以及最多允许存在多少雪”。而灵活协作,目的是打造合理、公开、透明、高效、低成本的可持续治理局面,在为业务增长和创新提供更多包大小空间的同时,将整包大小控制在预期范围内。
分析技术分析技术,是秉承Byte级“较真儿”精神,以包大小真实占用为指导原则,公平公正童叟无欺,实现对不同颗粒度(元素、功能、业务、团队)的包体积占用度量,并在此之上提供切实有效的可瘦身项分析能力,用于指导和评判瘦身情况。先来回答一个问题:分析工具为什么很重要,很重要?
首先,既然要进行包瘦身,那么各种不同的“大小”就如影随形,例如:“xxx模块多大?”、“xx功能/业务占多大”、“最新版本apk相对上个版本增加了1MB,不同功能的变更带来的大小变化分别是多少?”、“我今年的目标是将apk减少10MB,可以通过哪些瘦身Action来达成目标?”。工程领域有几句著名论断:“无度量不改进”、“无度量不管理”,包大小分析工具的核心价值之一就是提供这个度量:各种不同颗粒度的度量,小到一个java类、资源、so,继而到一个模块(jar/aar)、再到一个独立功能、业务、甚至是团队。由此衍生开来,既然有了度量,那么就可以进行有效的责任归属、瘦身目标制定、效果预估等,是不是豁然开朗?
其次,在具备度量能力基础上,站在长远角度考虑,实际瘦身治理过程中还需要能够回答“这个app是不是已经瘦到极限,还有哪些地方可以瘦身?”
分析工具需要具备的另外两个重要价值是:指导和评判。这二者其实是一个事务的不同视角,即:对主动者给予指导,对被动者给予评判,在瘦身前用于指导,在瘦身后用于评判。
一个满足有效的度量、指导和评判需求的包大小分析工具,该具备怎样的自我修养,这就是本章所要探讨的内容以及会给出的答案。在优酷,这个Android端包大小分析工具的名字是franky,潦草诞生于2020年初,逐步迭代完善/增强至今已2年有余,趋于成熟,仍在前行,目前正在筹备开源中,希望能给Android包瘦身治理带来一些帮助。
3.1 方案设计
本章将围绕“度量、指导、评判”这几个核心价值进行方案设计。首先不妨采用问答的方式,来进行具体分析和拆解的推导过程。
度量的对象是谁?度量的值又是一个什么样的值?在包瘦身不同场景,关注的对象也不一样,颗粒度最细的是apk中各种元素(java类、java资源、十几种不同类型的Android资源、动态链接库so),再往上层的是模块(jar/aar),继续往上层则是功能(由多个模块组成的独立完整功能)、业务(由多个功能组成的完整业务)、团队(组织架构中一个团队负责的所有业务),再继续往上就是apk了(这个没有什么意义,一个文件的大小根本不用什么分析工具),当然一个小型app可能在模块之上仅需要一层功能聚合就足够了。至于度量的值,则是在apk中的真实大小占用,即删除后apk可以减少的大小,对于某些元素原始大小和对apk大小的真实占用之间,存在着非常大的差距,这个差距会导致对瘦身Action的效果评估出现不可忽视的误差,从而使瘦身Action的优先级排序、指导和评判失去根基。由此得到“度量”的需求拆解结果是:提供元素、模块、功能等不同颗粒度,在apk中真实占用大小的度量。
指导的内容有多具体?评判的依据又是否公平、透明?瘦身的指导,如果只提供一个大概的方向,是远远不够的,需要非常明确、具体、可操作。举个例子:如果我只告诉你“充分利用proguard,精简优化keep规则,让更多的类被裁减和混淆,就可以有效瘦身”,那么如何让参与app开发的所有同学,都能够据此高效的完成这项瘦身任务?但是如果能够给出“你负责的业务/功能/模块,类未混淆率是80%,数量是600个”,相比前者显然更具有指导性,更进一步,假设还能够给出“每个未混淆类,是被哪些keep规则所影响”,是不是实际瘦身过程变得更加有迹可循,相信任何一名开发同学都能够很好的完成这项工作。再举个例子:“缩减或远程化大尺寸图片,可以有效瘦身”,与“你负责的这个模块,对apk真实大小占用超过10KB的图片,一共有10个,分别是xxxx”,这二者相比显然后者更具指导性。再来说说瘦身的评判,不能依靠人的能力和判断力,这样很难做到公平、透明,需要通过可量化的数据作为依据。由此得到“指导&评判”的需求拆解结果是:提供明确、具体、可量化、可操作的可瘦身项分析,用于对瘦身过程进行指导和评判。
此外,还有两个非常现实的问题,也不可避而不谈。分析覆盖率(能够找到模块归属的元素大小之和,占apk大小的百分比)能够做到多少?对于分析覆盖率,理论值就应该是100%,apk构建过程没有magic,所有在apk中存在的元素皆有来源。当一个元素(比如资源、so)被多个模块(这里的模块是广义上的模块,例如app工程、subproject工程)包含时,这个元素归属到每个模块的大小怎么计算?从公平的角度,多模块包含的重复元素,归属到每个模块的大小应该是等比例分担(Proportional Set Size)的。
根据上面的推导过程,我们来进行提炼和总结:
现在,如果让你来回答以下几个问题,是不是就可以信手拈来,轻松惬意?
- 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考虑并实现的构建环境兼容性,或许更能够对这个问题获得直观的认知: