Soul App 一直在关注和优化基础指标。其中包大小优化也是长期关注的指标。众所周知,App 安装包大小不仅仅影响用户下载体验和流量,对于 App 推广也具有一定的影响。在包体各项优化措施中,so 减体始终是绕不开的话题,为此也想分享下Android 端在 so 治理的一些经验。
以下针对 so 压缩的具体流程方案。整体可分为编译期和运行时两阶段。
目标 so 选择:并非所有的 so 都适合进行压缩,挑选的基本原则是尺寸较大,对加载时机要求不高。得到目标 so 之后可以纳入白名单配置中,供后续使用。
压缩:为了自动化压缩 so,我们可以选择在编译器自定义 task 的方式,既等mergeJniLib 执行后,执行自定义的压缩 task:
读取 so 的压缩配置,并且遍历 so 的构建产物。
判断 so 是否在白名单中,如果不在白名单中,则忽略,如果在白名单中,则使用 7z 压缩到指定路径,再将原始 so 删除,并将 md5 等信息生成一份指定的文件;
自定义 transform 插件,使用字节码编辑工具(ASM&Javassist均可),将不在白名单中的 System.loadLibrary 替换成自己的方法,切记,自己的方法中会调用System.loadLibrary 处理,需要加入黑名单。
打开 App,先去读取生成 so 的文件,获取 so 的关键信息;
因为 System.loadLibrary 已经换成的自定义的方法,所有的 so 加载都会走到我们的自定义方法中,判断so在不在白名单中,如果不在,则走System.loadLibrary 方法,并上报日志;
如果在白名单中,判断之前是否解压过,如果解压过,判断 md5 是否一致,如果一致,则 System.loadPath 方法,如果不一致,重新解压到系统so目录&使用System.loadPath 加载,并记录状态和上报埋点;
如果没有解压过,则先解压到系统 so 目录,在使用 System.loadPath 加载,并记录状态和上报埋点。
原始大小 | gzip压缩后 | 7z压缩后 | 收益率 |
13.3M | 4.9M | 3.4M | 30.6% |
6.5M | 2.8M | 2.2M | 21.4% |
6M | 2.7M | 1.8M | 33.3% |
so 能减少25-30%,还能配置远程化,减少包体还能减少用户的下载耗时;
统计各个 so 的性能,失败率指标;
可以有效的管控 so 的加载时机。
多做一次解压操作,性能会有影响,需要选一些后置加载的 so,尽量不影响启动;
对打包性能有一点影响,基本可以忽略不计。
so 远程化
so 远程化方案,整体来说就是将原本打在 APK 包里的 so 库,从 APK 包剥离出来,然后上传到自己的服务器;当用户打开 App 时,在找合适的时机下载 so ;使用时加载 so 。
整体方案如图
借助于 gradle 打包配置,打包过程中,我们可以通过以下方式轻松剔除掉需要远程的 so 。
packagingOptions {
// 64 位 so
exclude 'lib/arm64-v8a/xxx.so'
// 32 位 so
exclude 'lib/armeabi-v7a/xxx.so'
}
so 的下载以及解压不是特别的重点,这里我们就不展开说了;同时动态加载 so 目前全网资料众多纷繁,这里也就不再重复了。
方案整体看似简单,但其实我们有踩过不少坑。
我们经常发现线上有极少量偶现的 so 加载失败等相关异常;仔细分析之后才发现我们下载 so 时,只校验了压缩过的 so 文件的 md5;最终解压需要加载的 so 我们缺少 md5 校验,导致了少量的 so 加载失败等相关异常;由此可以知道,线上的环境复杂,我们一定要校验最终解压完的 so 的 md5 是不是正确,这样才能保证 so 是可用的。下图可以更清晰的看出问题所在。
由于 so 远程化后,App 运行时需要从 CDN 下载 so 文件,导致我们 CDN 带宽增加较多;而彼时正处在降本增效的大背景,所以我们对于下载 so 导致的带宽增加也进行了优化。
说到优化带宽的思路,业内最常用的方案是使用的时候才下载文件,使用的时候才下载文件,就能最大程度减少流量;然而如果全量采取这个方案,意味着用户体验上,在进入某个被远程化的 so 的相关业务场景时,需要用户等待下载 so,有明显的 loading 过程;特别对于一些较大的so,或者用户网络环境较差的情况,用户可能直接退出等待;增加了进入功能的跳失率。
所以我们需要在以下两个方面取得平衡:
降低 CDN 带宽峰值,尽量使用时下载。
在 CDN 带宽峰值可接受的范围内,尽可能的让用户体验更好。
基于以上两点,我们做了流控方案,流控方案主要思路:通过削峰填谷充分利用 CDN 波谷的时间段进行预下载,使得全天的 CDN 带宽更加平滑,进一步降低 CDN 费用。
带宽优化的流控方案如下:
在实际配置中,我们观察在晚上下班后,CDN 带宽会明显上升;于是我们配置 晚高峰期间,控制一定比例的用户不在 App 冷启动时进行 so 文件预下载;其它时间则都保持预下载;这样既尽可能的降低 CDN 费用,也最大程度的兼顾了用户体验。
[{
"time": 20, //当天时间
"rate": 50 //对应时间 App 冷启动不进行 so 文件预下载的比例
}, {
"time": 21,
"rate": 50
}, {
"time": 22,
"rate": 50
}, {
"time": 23,
"rate": 50
}]
下图可以看到流控的对比效果,对于 CDN 带宽消耗高峰期的流量尖刺可以进行有效的控制:
可以看到 so 的优化能够快速降低包安装大小,尽管远程化会带来一些 CDN 的损耗,但是通过合理的方式进行规避,基本上可以控制在可接受范围内。so 优化手段不仅限于文章描述的方式,还有很多的方式可以挖掘,后续也会持续分享相关的经验。