2018年谷歌I/O开发者大会给了一张图,它统计了Apk Size与安装转化率的关系,体积越大的应用其安装率转化率就越小,因此,体积优化对于产品具有明确重大的商业价值。
2021财年,Hummer初步投入人力到体积优化中,如果说一开始我们只是管中窥豹打鸡血,那如今我们已经能胸有成竹身轻盈。
2022年度,我们以引擎为核心、利用好社区和周边的技术、打造了个性的工具和指标、建设了生态平台、产出了多个独门绝技,完成了围绕flutter技术产品体系化的体积优化。
量化优化的收益,从1.17到1.22.6.3为止,用上所有优化点的业务可以在引擎库、业务产物、资源三大块各产生10%-50%左右的体积减少。
Flutter体积相对于Native增长的问题主要有两个:一次性产生的 Framework/Packages增量、业务代码膨胀后的分包和动态化需求。
如下图所示,Native技术有了操作系统的加成,可以直接使用操作系统提供的UI Framework构建界面和渲染;而Flutter是必须要带上引擎和渲染框架这部分的(即下图的1和部分2),不同的业务还会加上各异的简单或复杂的三方界面组件,这就是很多业务转到Flutter技术后遇到的体积第一次增长的问题。不过我们也看到,这部分其实是一次性的成本,可以通过技术优化或者“南水北调”工程进行“风险对冲”。
随着业务的增长体积也在膨胀,Native技术可以通过分包动态加载(仅android)方式应对,Flutter在2.x上也开始支持分包但需要搭配谷歌Play Store才能实现,这不适用于有更加灵活部署需求的集团应用。
针对上面我们对Flutter技术产物的拆解,我们在三大模块上定下了30%的优化目标,并且通过本地和线上指标对产物进行持续监控,面对未来准备了分包动态化技术。目前引擎优化已经大部分完成,部分监控指标和平台功能也初具雏形,动态化技术也经过了年多的验证具备实战能力,接下来将详细介绍一下这一年的产出。
在Android平台上icu库是必备的基础设施,那么把flutter引擎内置的字体模块裁剪并使用系统提供的功能则具备了可能性。
通过借鉴U4内核沉淀的裁剪经验,我们成功的把字体模块的实现进行了部分裁剪,获得了不错的收益。
Flutter内置了安全和网络模块,但通常业务都集成了更为强大的网络库,于是我们就可以考虑替换的可能性。
同样基于U4内核沉淀的裁剪经验,相关模块可以通过打包时开启宏开关一健裁剪掉,然后需要对接上网络模块即可正常加载网络资源。
默认情况下,iOS平台使用了兼顾了性能和体积的编译参数进行优化优化,但在一些特殊的业务,体积具有更大的优先级,于是我们提供了一个需要时指定打开的选项,为这些业务提供体积取向优化。通过妥协一定的首页性能损失换来极致的体积减小,随着升级新设备的性能提升对冲越来越体现价值。
动态库通常包含数据部分和指令部分,早期苹果应用商店的加密策略使得包的大小居高不下,通过将数据部分进行提前压缩并在运行时解开,该技术集成在链接器并自动内嵌了压缩和解压缩逻辑,成功地避开了苹果系统的弊端,产生较大的体积收益。
感谢淘宝的同学分享了该工具,Hummer通过集成了该工具,并通过手动设置宏开关打包开启,在iOS上可以很好地解决问题,对比获得的体积收益,一次性的解压缩成本还是可以接受的。
自从2021财年集成了LLVM之后,我们能做的事情越来越多了,不再局限于做性能优化,我们在体积上也得到了非常大的收益。
2.1.1 LTO(Link Time Optimization)
没有被调用的函数为什么还要存在于AOT产物中?
Dart原生的编译器基于JIT的流程改造,没被调用的函数是不能随便删除的。但我们定制LLVM产生的AOT很明确的知道这些函数是可以被删除的,通过更好的inline计算,在满足性能的基础上做更好的体积优化,我们获得接近10%的收益。
2.1.2 Box/Unbox优化
Dart语言支持Boxing对象,Boxing类型可以和原生类型相互替代使用,于是就有了在Boxing形态和原生形态自动进行box和unbox的内置行为。
在Dart的原生编译器中,复杂场景下该编译器不能很好地识别对象的类型信息,从而多了很多冗余的box和unbox操作。通过LLVM流程框架,我们构建并一直传递丰富准确的类型信息,在最后通过合并算法去除冗余的box和unbox操作,进而优化了体积。该收益接近1%。
2.1.3寄存器传参和CSR
通过利用寄存器传参数,以及调用者保存寄存器约定的优化算法,我们可以将生成的函数调用代码体积降到更小,对于像dart这种小方法特别多的异步语言来说具有很大的价值。
iOS平台同样可以使用淘宝提供的链接器工具进行后期处理,以对动态库产物的数据部分进行自动压缩和解压缩。
通过修改业务产物的生成流程,对接该工具后在iOS平台上也可以获得不错的体积优化。
业务在开发过程中比较常见的资源是文本、图片、字体、图标、二进制等等。其中图标字体文件有官方的工具支持精简只要照做即可,但仍然遇到业务反馈可能需要人工干预的情况,另外还有对文本例如NOTICES文件的处理,我们做了一些定制,在2021财年已经完成。剩下图片通常是资源里面最大的一部分,2022财年主要解决图片的体积优化问题。
每一个业务其资源所面临的问题可能都是一样的,不同的客户端支持、不同的分辨率支持、不同的网络条件的支持。
因此,作为背景的图片必须满足当前最好的设备的分辨率。
因此,作为关键路径的物料必须内置在发布包中。
因此,图片的格式必须是大众的,png就很好,webp就很小众。
前面两点我们Hummer很难帮业务做优化,在flutter内置支持多个图片类型的基础上,第三点我们找到了办法。
通过人肉眼不可感知的质量损失,来换取图片更改格式、更改压缩参数来达成尽可能大的体积优化,为此我们实现了一个图片优化工具,并集成在Hummer的打包流程中可通过选项自动完成图片优化,在不同的业务使用能压缩10%-50%体积。
如开篇所说,Flutter官方实现了一个分包技术,但需要Play Store的支持才能实现动态下载。
为了在集团很好的落地该技术,我们定制了Defferd Components的实现,业务可以通过我们的定制完成分包的构建,对接好后台发布等支持设施后可实现应用分平台和功能递增式下发非主线功能。
本财年是Aion蜕变的一年,通过持续优化、业务验证、反复完善,如今Aion已经备全了实战的基础设施,做到原生Dart语言的动态化,并且支持动态下发解释运行的性能。该技术方案是Hummer主打解决未来业务膨胀体积增长的绝技,更多参考后续的Aion文章。
以上的优化解决的都是物(项目代码)的问题,人会不会出问题?
我们发现,不同写法的代码产生不一样的体积,不同的习惯可能导致有些人有些团队产出的体积偏大,因此我们需要找到一个指标来表达代码产生的指令密度。
该优化通过编译选项打开,然后产生一系列的量化指标,可供业务做二次分析用途。在实践中,它一出手就帮业务找到一处脚本生成的代码有大量的冗余赋值操作,从而节约了2/3的指令体积。
随着时间的推移,众多功能的引入,又或者团队成员犯错写了bug,关键产物的体积可能增长。
为了杜绝此类问题,我们这一财年实现了本地提交的体积监控,用于保证Hummer的引擎的体积优化一直是处于良好状态的。
Flutter作为Native技术的有力竞争者,体积劣势已渐渐被攻克不再是短板,未来我们Hummer团队会继续跟进业务的主要痛点问题,为业务创造更大价值奠定基础。