前言:
随着业务的快速迭代增长,App里不断引入新的业务逻辑代码、图片资源和第三方SDK,直接导致APK体积不断增长。怎么来优雅的解决包体积问题呢?接下来就带来我的一些见解。
在新氧8.24.0版本的对比情况
一. 为什么要优化包体积
1.下载转化率
从图来看,安装包越小,转化率越高的结论依然成立。一个100MB的应用,即使用户点击了下载,也可能因为下载时间过长,网络速度慢,手机存储紧急等原因而中途终止下载,正如图中所描述,安装包大小与转化率的关系是非常微妙的。
2.推广成本
包体积对渠道推广和厂商预装的单价会有非常大的影响。特别是厂商预装,这主要是因为厂商留给预装应用的总空间是有限的。如果你的包体积非常大,那就会直接增加公司市场推广成本。
3.安装时间
文件拷贝、Library 解压、编译 ODEX、签名校验等,新氧app的dex数量高峰时存在24个dex,在android5.0,6.0系统的低端机型上安装时间长达2分钟左右,给用户带来不好的安装体验
4.运行内存
Resource 资源、Library 以及 Dex 类加载这些都会占用不少的内存。新氧app的初始运行内存就高达160MB。
5.存储空间
100MB 的安装包,启动安装解压之后很有可能就超过 200MB 了。对低端机用户来说,也会有很大的压力。
6.业务梳理
删除无用或者低价值的业务,回顾过去的的业务,不能只顾着往前冲,适时地还一些‘技术债务’
二. 如何优化包体积?
首先,我们来看看安装包的组成,无非就是Dex、Resource、Assets、Library 以及签名信息这五部分,包体积优化就是对这五部分进行优化的过程,接下来就进行优化阶段。
1.Dex优化
1.代码混淆ProGuard
常言道,“十个 ProGuard 配置九个坑”,特别是各种无良的第三方 SDK。面对ProGuard 配置文件,我们需要仔细检查,寻找是否存在过度keep的现象。
实际上,很多情况下我们只需要 keep 其中的某个包、某个方法,或者是类名就可以了。值得注意的是针对被反射使用到的类需要被keep。
我们还可以尝试开启R8的完全模式,在完全模式下能更加彻底的删除无用代码
2.去掉 Debug 信息或者去掉行号
某个应用通过相同的 ProGuard 规则生成一个 Debug 包和 Release 包,其中 Debug 包的大小是 4MB,Release 包只有 3.5MB。
既然它们 ProGuard 的混淆与优化的规则是一样的,那它们之间的差异在哪里呢?那就是 DebugItem。
DebugItem 里面主要包含两种信息:1,调试的信息。函数的参数变量和所有的局部变量。2,排查问题的信息。所有的指令集行号和源文件行号的对应关系
DebugItem有多大? 占了dex文件的5.5%左右,去掉DebugItem不影响整个的运行逻辑和性能,甚至还能降低一点运行内存。
如何删除DebugItem? Facebook 的一个开源编译工具ReDex可以做到去掉DebugItem,感兴趣的可以去认真研究下
3.Dex分包
新氧apk高峰期中有24个dex文件,为啥会有这么多的dex文件呢?原因是
“define classes and methods”是指真正在这个 Dex 中定义的类以及它们的方法。而“reference methods”指的是 define methods 以及 define methods 引用到的方法。
从图上来看,有2800多个方法是引用其他Dex的方法,这些跨 Dex 调用会造成一些冗余信息,这些冗余信息又会造成method id 爆表,信息冗余等问题。
定义一个Dex 信息有效率的指标, 每个 Dex 的方法数都是满的,即分配了 65536 个方法。保证 Dex 有效率应该在 80% 以上。
Dex信息有效率 = define methods数量/reference methods数量 reference methods数量 = 65536
那如何实现 Dex 信息有效率提升呢?关键在于我们需要将有调用关系的类和方法分配到同一个 Dex 中,即减少跨 Dex 的调用的情况。Facebook的ReDex使用的是贪心算法计算局部最优值。感兴趣的可以去认真研究下
2.Native Library优化
1.Library压缩
在默认的 lib 目录,我们只需要加载少数启动过程相关的 Library,其他的 Library 我们都在首次启动时解压。对于 Library 格式来说,XZ 或者 7-Zip 压缩的压缩率同样可以比 Zip 高 30% 左右,效果十分惊人。压缩方案的缺点在于首次启动的时间。
2.Library动态加载
目前市面上有大量成熟方案,比如SoLoader, ReLinker等,我们可以借助这些成熟方案来实现so的动态加载,不过我们要注意安全性问题,对外部路径下so文件必须要做安全性检查。还有处理好版本控制问题,做到so文件和APK版本一致。so文件动态加载的优化效果十分显著,同时也要注意崩溃率,用户体验等问题
3.去掉无用so文件
随着业务的不断更新换代,有些业务已经被弃用了,但是无用so文件还依旧保留在项目中,如果没有时常对so文件进行筛选,这些无用so文件会一直在项目里吃灰,占用了很多体积。在新氧做包体积优化期间,筛选并去掉了11个so文件,节省了大约5M的体积。 为了不用每次等到包体积专项整治的时候才能做这个工作,项目中需要健全so文件增量机制,将so文件文档管理等措施来完善
3.Resource优化
1.图片资源优化
项目中存在了高达十几M的图片资源,单张图片最大的有超过600Kb的情况,怎么让这些大体积图片在设计能接受的情况下变小呢?答案是压缩和格式转换,对png图片进行压缩操作,tinyPng就是把相似像素的24bit位用8bit位来表示,并且移除了不必要的元数据,肉眼很难看出来变化。格式转换:将没有透明通道的png图片转换成jpg图片,图片体积上会有极大的提升,也可以将少量透明通道的png图片转换成webp图片,也能做到图片体积的减少。不仅是要做到对存量图片进行处理,也要对增量图片进行防范,制定增量规则,对增量图片自动化压缩,大体积图片警告等措施来完善
2.资源混淆和极限压缩
资源混淆的思路其实非常简单,就是把资源和文件的名字混淆成短路径。这样做的好处是 1.资源索引文件 resources.arsc 需要记录资源文件的名称与路径,使用混淆后的短路径 res/s/a,可以减少整个文件的大小。2.metadata 签名文件记录了所有文件的路径以及它们的哈希值,使用短路径可以减少这两个文件的大小。3.ZIP 文件格式里面也需要记录每个文件 Entry 的路径、压缩算法、CRC、文件大小等信息。使用短路径,就可以减少记录文件路径的字符串大小。
资源文件有一个非常大的特点,那就是文件数量特别多。以新氧 8.26 为例,安装包中就有 13000 多个资源文件。
apk使用的还是 Zip 算法,AndResGuard利用了7-Zip 的大字典优化,APK 的整体压缩率可以提升 3% 左右。同时也支持针对 resources.arsc、PNG、JPG 以及 GIF 等文件的强制压缩。
3.无用资源
通过长时间的迭代,项目总会有一些无用的资源,尽管它们在程序运行过程不会被使用,但是依然占据着安装包的体积。那我们就可以使用Lint静态代码扫描工具来进的无用资源了。不过项目里还存在很多动态加载资源,跨module使用资源的情况,导致这部分资源需要谨慎处理。
三. 怎样持续优化包体积?
1.制定规范
规范永远都是让开发效率时半功倍的,不管是代码规范,资源规范,接入第三方库规范等都能让项目的包体积得到改善,比如除特殊情况,超过50kb的图片不得添加到项目中,调研第三方库时对带来的体积进行审核,代码尽量做到复用性,而不是省事的复制一份,无用业务的删除资源,超过2M的功能需要审核等操作都能有利于包体积优化
2.持续监控
对于包体积,如果一直放任不管,几个版本后就能给你一个很大的惊喜,通常有这些方式进行监控:
1.大小监控:每个版本跟上一个版本包体积的对比情况。如果某个版本体积增长过大,需要分析具体原因,是否有优化空间。
2.依赖监控:每一版本我们都需要监控依赖,这里包括新增 JAR 以及 AAR 依赖。这是因为很多开发者非常不细心,经常会不小心把一些超大的开源库引进来。
3.规则监控:将包体积的监控抽象为规则,例如无用资源、大文件、重复文件、R 文件等,比如使用微信开源的ApkChecker实现包体积的规则监控。
3.监控的自动化和平台化
包体积的监控最好可以实现自动化与平台化,作为发布流程的其中一个环节。不然通过人工的方式,很难持续坚持下去。