1.概述
在 2018 年的 Google I/O,Google 透露Google Play 上安装包体积与下载转化率的关系如下图:从图中我们可以看出来随着apk的体积增大,转化率逐渐下降。而包体对应用的影响主要有以下几点:
- 下载转化率:一个近100M的应用用户在点击下载之后,因为网速过慢,用户会在犹豫的过程中取消下载,而一个10M的应用在用户犹豫的过程中却已经下完了。
- 推广成本:
- 应用的预装对单个apk的体积有限制,apk的体积会影响到应用的单价。
- 一些移动开发者会把apk置于官网下载,体积过大不仅使得用户浪费流量,也使得开发者服务器流量浪费。
- 应用市场:
苹果已经限制当应用体积大于150M的时候只能WIFI下载,而谷歌市场要求当应用apk大于100M的时候使用 apk扩展文件上传(可参照之前的文章了解),此也为应用商店对带宽压力的一方面考虑。
在现如今众多的超级app随着业务的不但增加,需求的不但演进apk体积在不断增加,apk体积优化闲得尤为重要。google play上有一款应用via浏览器,因为其应用体积小,功能相对齐全。优势明显比其他同类品牌的产品强出不是零星半点。
2. apk打包流程
既然是优化apk体积,追本溯源,当然是从apk产生的过程来分析,在网上我们可以查到apk的组成主要由资源,代码和第三方库,如 so 库等部分组成。开发过中主要是通过ant、gradle等方式编译生成安装包,具体流程如下:有此图我们大致可以看出apk体积的优化我们可以从resources,dex和其他资源库三个方向去优化。将apk解压或者通过Android Studio等工具来解压,主要关注以下几个方面:
- dex个数和每个dex方法数的情况
- 没有alpha通道的png图,可压缩成jpg减少体积;
- 超过一定数值的大文件,特别是图片资源可采用有损压缩;
- 新增文件、减少文件,文件大小发生变化的情况;
下面我们从上述流程中提到的几个方面去分析apk瘦身的方法。
3. Resources优化
Android资源管理框架实际是由AssetManager和Resources两个类来实现的。其中,Resources类可以根据ID来查找资源,而AssetManager类根据文件名来查找资源。事实上,如果一个资源ID对应的是一个文件,那么Resources类是先根据ID来找到资源文件名称,然后再将该文件名称交给AssetManager类来打开对应的文件的。而Resources是通过resources.arsc把Resource的ID转化成资源文件的名称,然后交由AssetManager来加载的。关于resources.arsc的格式可以在Android源码“/frameworks/base/include/androidfw/ResourceType.h”下查看。
- 删除无用资源:
在不停的迭代中,或多或少会出现无用的资源,包括但不限于xml、png、id、string。查找无用资源主要使用lint的UnusedResources以及UnusedIds两个检查规则,但是针对多library结构,官方的lint在某些方面不符合我们的要求,所以我们修改了一些地方。对于使用gradle的编译的项目我们可以使用以下方式打包:
android {
...
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
- 图片处理
- 对于体积特别大(超过50k)的图片资源可以考虑有损压缩,jpg采用优图压缩,png尝试采用pngquant压缩,输出视觉判断是否可行;
- 对assets中的图片资源也使用aapt的crunch做图片预处理;
- crunch有可能会使图片变大,在这种情况,我们可以替换成原图。需要注意的是对于.9.png,由于crunch过程中去除了黑边,所以不能替换;
- 对于没有透明区域的png图片,可以转成jpg格式。
- 采用如webp,svg等格式文件。
关于图片压缩可以采用一些第三方的网站压缩如熊猫推荐一个自己写的脚本ImageProcess通过该网站批量压缩图片。
- 字符编码
resources.arsc中的会有一个去重过的字符串资源池(相同的两个字符串其实用的是同一份),每个String使用偏移值来获取资源池中的数值。ResourceTable的编码对于resources.arsc的体积有很大影响。这样的好处就是处理纯英文等直接通过ascii存储语言的国家资源文件将会更小,而对于中文、日文这些国家的资源文件有可能会变大。 - 指定文件的压缩方式与7zip压缩
安装包是一个压缩文件,我们可以指定里面的文件采用哪种压缩方式,比如一些文件我们可以采用一些极致压缩到最小,然后安装成功后再解压,或者安装完成后从服务器下载再解压,但此举会对启动速度有一定的影响。 - 语言包动态加载
- 资源混淆,我们常常用proguard来混淆代码,同样资源也是可以混淆的,会生成a,b,c命名的png 和xml,让反编译更加难受,不过这其中需要注意资源映射问题。
- 产品做减法。
4. Dex 优化
对于大部分应用来说Dex的个数和Dex的体积是整个应用的大部头,因此Dex的优化在对包体优化起到了关键作用,主要有以下几个部分:
- ProGuard 混淆
你可以通过下面的方法输出 ProGuard 的最终配置,需要注意各种的 keep *,很多情况下我们只需要 keep某个包名,方法名,类名即可。
-printconfiguration configuration.txt
但我们可以加大力度混淆,比如对非export的四大组件和View进行混淆,但我们要注意以下两个方面的问题
xml
清单文件中的xml需要同步修改
代码替换
代码中不可出现运算拼接出来的类名
// 情况一:变量
public String activityName = "com.sample.TestActivity";
// 情况二:方法体
startActivity(new Intent(this, "com.sample.TestActivity"));
// 情况三:通过运算得到,不支持
startActivity(new Intent(this, "com.sample" + ".TestActivity"));
饿了么曾经实现过一款混淆四大组件的库Mess,具备一定的参考价值。
Android Studio 3.0推出了Android新Dex编译器D8与新混淆工具R8可以在编译过程中生成的dex大概减少3%,但此功能还在测试阶段。
- Dex 分包
正如我们所知同一个应用可能会存在多个Dex包,每个Dex 包的也会存在相互之间的引用,因此在Dex的分包规则上我们同样具备优化的方法,比如避免Dex包之间的冗余和Dex包生成的算法优化。具体的我们需要先了解Dex文件的格式和生成规则,其中主要是优化Dex的格式中debugItems模块。
5. 其他资源优化
一个应用除了Java代码,resource等资源之外,还存在一个Native library等相关资源。这块主要是针对一些Native 资源进行优化。
- 减少冗余
例如一些so库中C++运行库大多使用静态编译方式,使用stlport_shared方式可减小APK包大小,相当于把大家公有的代码提取出来放一份,减少冗余。同时也会节省一点内存,加载so的时候动态库只会加载一次,静态库则随着so的加载被加载多份内存映像。主要为了减少冗余模块。大家都用到的一些基础功能,应该抽成基础模块。 - so库压缩
我们可以针对一些so库进行特殊压缩,需要时解压,甚至可以从网络获取到本地进行加载。
以上为对apk体积优化的一些总结,当然会还有一些其他的可以优化的点,欢迎大家补充。
针对资源压缩主要是目前浏览器引用微信的AndResGuard有着明显的效果。
参考
极客时间Android开发高手课
Dex文件格式详解
美团Android App包瘦身优化实践
支付宝Android 包大小极致压缩
微信Android安装包相关知识汇总
FaceBook Redex