I Focus on optimizing app performance especially decreasing package size
我将分享一些包体积优化的通用思路(大纲)
后续还会补充一些技术文章如so上云方案、so压缩方案、apk内资源路径缩短、包体积监控治理平台建设等
优化蓝图:
一、我们为什么做包体积优化?
1)Google官方数据
reference: medium.com/googleplayd…
- 谷歌内部统计数据表明:越小的apk安装包体积与越高的apk安装转化率相关
- 低于
100M的应用,安装包每增大6MB,对应的apk安装转化率下降1% 10MB左右的应用下载完成率比100MB左右的应用高出30%
基于google官方公布的数据,我们发现应用的包体积大小对于apk下载成功率以及用户的安装转化率有着极其重要的影响,能够直接影响到用户是否愿意下载这个应用,进而影响到app的活跃用户
2) 厂商合作(预装)
如果app与手机厂商有一些合作比如预装(手机出厂时预先安装好app,用户无需下载),那么大多数厂商都对app的包体积大小有着严格的限制,同时越大的包体积通常需要越昂贵的预装价格,这些都反映了做包体积优化的必要性。 预装价格通常与以下元素相关;
- app包大小
- app安装在手机的桌面还是文件夹中
- 用户激活率
当然如果来了某个厂商预装需求,但是此时app的包大小不满足厂商的预装要求,那么恭喜你,接下来的时间会变得非常忙了。常见的临时解决方法是
- 挑选体积较大的so上云或者压缩
- 裁剪非核心业务线(需要引导用户下载完整版app)
- ....
二、如何做包体积优化?
如何做包体积优化是一个很难回答的问题,就好比一个人如何变得有钱,很难一下子把这个问题回答的很好
- 技术硬实力
技术硬实力在做性能优化,特别是包体积特别重要,需要的技术栈繁杂且深入如gradle、AGP、Android资源运行时加载流程、so的加载流程、dex、c/c++、各种黑科技、系统源码....
- 思考
思考在如今的背景下(原生、RN...)等如何做包体积优化,在有无限业务需求迭代的情况下,如何真正解决包体积持续增长的问题,是否考虑技术架构转型比如转跨端ReactNative,业务需求的迭代最终如果能实现动态下发,最终解决体积无限增长的困局呢。
1)优化思路:
解包看apk下到底有那些内容,从结果向前推或许是个不错的思路
| 目录/文件 | 说明 | 收益 |
|---|---|---|
| classes.dex | Dalvik 字节码文件,包含应用程序的所有 Java 类。通常一个 APK 至少包含一个 dex 文件。 | 收益高、技术难度高 |
| AndroidManifest.xml | 应用的核心配置文件,包含包名、组件声明、权限等信息。解压后为二进制格式,需要使用工具解析。 | 几乎无收益 |
| META-INF/ | 包含签名和校验信息: - CERT.RSA:签名证书 - CERT.SF:签名的元信息 - MANIFEST.MF:校验文件。 | 收益小、技术难度高 |
| res/ | 资源文件目录,存储布局 XML 文件、图片、动画等资源,未经过编译的部分。 | 收益高、技术难度高 |
| lib/ | 包含原生库的目录,以 CPU 架构分类(如 arm64-v8a, armeabi-v7a),每个目录下是 .so 文件。 | 收益高、技术难度高 |
| assets/ | 应用的原始资源文件夹,存放未编译的资源或数据,应用可直接读取此目录内容。 | 收益高、技术难度高 |
| resources.arsc | 已编译的资源索引文件,包含字符串、样式等的二进制表示,供应用快速访问资源。 | 不让压缩后收益一般、技术难度高 |
| kotlin/ | (可选)Kotlin 编译器生成的文件目录,用于支持 Kotlin 应用。 | 未做调研 |
2)哪些优化项ROI高?
1. 开启代码混淆、无用代码、资源删减,开启代码混淆后包体积会下降很多,只需要开启混淆开关,几乎无风险
minifyEnabled true
shrinkResources true
2. 分架构打包64/32位
我们的app中可能会包含很多so文件,但通常每个so都会对应几种abi架构,如果打包时不单独处理几种架构都会被打到apk包。
构建创建64位和32位的flavor,针对64位包和32位包分别打包,然后应用商店中分别上传,应用商店都支持,且鼓励分包上传的方式预计会得到15%+的收益
3. 设置extractNativeLibs=true
这个设置在AndroidManifest中声明,打包时会将so压缩,安装后解压到app的安装目录,会明显降低apk大小(如果so比较多的话),AGP高版本在build.gradle中换成了useLegacyPackaging编译选项,by the way extractNativeLibs在minSdk和AGP版本不同的时候,默认值不同。
4. 开启资源混淆、压缩
- 可以使用开源的
AndResGuard库做资源混淆,这会减少Resource.arsc文件的大小,同时META-INF也会减少。资源名称减少1Byte整包会有4Byte的收益(by the way AGP4.2+之后开启资源缩短的编译选项+使用AndResGuard会有BUG,这块我单独适配过,说起来都是泪啊)。另外AndResGuard也不仅仅是资源名混淆的作用,还包含重复资源复用、删除、资源再压缩、整包使用7zip重压缩,收益是非常客观的,做包体积优化这个库我是推荐必用的,但是很久没人维护了,对高版本的AGP可能不适配 - 资源方面推荐优先处理R文件内敛、png等图片转webp,这个做了收益会很大,且几乎无风险。推荐开源库
booster森哥写的,在持续维护
5. So上云
这块内容比较多,后续我可能单独写一篇文章详细讲解。目前的so上云方案都比较成熟了,网上基本也都大差不差,可以找找有没有能复用的。整体思路:打包自动抽离so ---> 运行时自动下载、按需加载
6. So压缩
这块也只提供下思路吧:这个compress${variant.name.capitalize()}Assets后
,拿到so目录,根据配置文件使用xz压缩so后放到zip文件中,app启动后将存放so的路径添加到classLoader对so的查询路径中,随后提供sdk接口给业务方调用加载so,也可以hook系统的load或者loadLibrary方法
7. 资源的其他处理
- json gradle打包插件压缩成1行,去掉空格和换行
- dpi:不同dpi目录下同名资源只保留最高清晰度
- xml:把内容压缩成1行去掉多余空格和换行
- gif:gifsicle处理
- md5:md5相同资源只保留一份
- png:pngquant
3)如何持续优化大型应用包体积?
这块能说的内容太多了,让我缓缓,后续补充....
三、极致优化手段及收益
进入到深水区后,普通的技术优化项已经没有太明显的收益了,下面说下项目中极致优化的大致收益。 117MB -> 84.3MB,共优化33.3MB,优化收益率28+%!优化收益还是非常可观的 包含
- so上云
- so压缩
- AndResGuard
- 图片转webp
- access内联、R文件合并等
- ........
但由于篇幅限制,本文中不会聊具体实现,只谈可获得实际收益,后续可能单开文章聊技术实现
1)资源路径缩短
apk文件本质就是一个压缩文件,我们可以将资源文件的路径进行混淆(缩短)来达到减少apk文件的终极目的。听着就很极致、就很卷对吧,没错就是这么极致!
可能很多人会问:为什么只是缩短资源的路径,而不是所有文件的路径。这里会涉及到Resource.arsc文件以及Android系统的资源加载原理,我们app对于非反射方式使用的资源,实际上在编译时aapt工具就已经将资源替换成了16进制的资源ID,而Resource.arsc文件维护资源id到真实文件路径的映射关系,大多数时候我们代码中实际都是用的资源ID,而非完整的资源路径。
这个优化我推荐一个库,AndResGuard腾讯开源,但是很久不维护了,需要自己适配高版本AGP。同时AndResGuard对于资源路径优化得不够彻底,仍然保留了文件夹,极致方式应该是去掉文件夹,同时还可以优化一个资源配置多个configuration、甚至可以优化资源后缀(xml会有些特殊),资源名同化resource.arsc文件中的资源名可以同化为一个字符例如"_", 这样就所有资源都用一个符号(arsc格式中存在于String常量池中,可以复用),优化还是有较高技术成本,需要熟悉AndResGuard源码,同时修改arsc二进制文件的内容
AndResGuard在AGP4.2+以上使用会有bug,4.2之后提供了资源路径优化的开关,但是与AndResGuard会有冲突,原因是AGP优化资源路径后,可能出现a.png和A.png,但是Linux和macOS中文件系统对于名称是大小写不敏感的,通过File.exist()等api判断的时候会认为两个文件是同一个,但其实不是。熟悉AndResGuard原理的同学知道,这个库会首先解压apk文件,然后copy到tmp目录,通过exist判断文件已经存在就会少拷贝文件,最终导致资源文件丢失,运行时crash。
收益
每个资源路径优化1byte,apk的实际收益会放大4倍,达到4byte,假设每个资源名优化10byte,共有8w个资源文件需要优化。
其中:
总收益为:
假设
将以上值代入公式,计算总收益:
这是资源缩短优化的优化收益,实际收益与理论基本匹配,平均每个资源优化字节数,与资源文件总数需要自己去预估,按照这个公式基本能预估到实际收益。
2)线上无用类分析
对于Android我们无法知道工程中有哪些代码已经不会用到了,这些不会用到的代码如果能下线会带来不错的收益。对这个优化感兴趣的同学可以看我的另一篇文章:juejin.cn/post/734606…
收益
根据dex文件解析,利用率比较高的dex如果包含8000-10000个类大小通常在3MB左右,也就是说如果下掉1w个类左右,我们是能得到3MB左右的收益的
下图是在包体积管理一站式平台搭建的无用类分析能力