|
|
|
@ -2,3 +2,167 @@ |
|
|
|
|
包体积优化 |
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
#### 目录 |
|
|
|
|
|
|
|
|
|
1. 前言 |
|
|
|
|
2. 包组成 |
|
|
|
|
3. 代码优化 |
|
|
|
|
4. Native Library 优化 |
|
|
|
|
5. 资源优化 |
|
|
|
|
6. 参考 |
|
|
|
|
|
|
|
|
|
#### 前言 |
|
|
|
|
|
|
|
|
|
现在 WIFI 很普遍,流量也很便宜了,优化包体积还有那么重要嘛? |
|
|
|
|
|
|
|
|
|
首先能想到的就是较小的包体积能够提高下载转化率,用户更愿意去下载,除此之外包体积与应用性能也是有挂钩的,表现在安装时间、运行内存、ROM 空间。 |
|
|
|
|
|
|
|
|
|
#### 包组成 |
|
|
|
|
|
|
|
|
|
优化之前,肯定需要了解包体积是由哪些部分组成的? |
|
|
|
|
|
|
|
|
|
![](https://i.loli.net/2019/04/01/5ca15e3a3181e.png) |
|
|
|
|
|
|
|
|
|
事实上,安装包无非就是 Dex、Resource、Assets、Library 以及签名信息这五部分,以下我们就针对这五部分进行优化。 |
|
|
|
|
|
|
|
|
|
#### 代码优化 |
|
|
|
|
|
|
|
|
|
对于大部分应用来说,Dex 都是包体积中的大头。对于这一块的优化,首先能想到的就混淆、压缩无用资源文件。 |
|
|
|
|
|
|
|
|
|
##### ProGuard |
|
|
|
|
|
|
|
|
|
一般在 release 版本,我们都会开启混淆,不仅能减少 Dex 体积也能增加反编译的难度。但是需要注意各种的 keep *,很多情况下只需要 keep 其中的某个包、某个方法或者某个类名就可以了。 |
|
|
|
|
|
|
|
|
|
可以通过以下方法输出 ProGuard 的最终配置: |
|
|
|
|
|
|
|
|
|
```xml |
|
|
|
|
-printconfiguration configuration.txt |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
那还有没有哪些方法可以进一步加大混淆力度呢?可能就需要向四大组件和 View 下手了。一般来说,应用都会 keep 住四大组件以及 View 的部分方法,这样是为了在代码以及布局中可以引用到它们。 |
|
|
|
|
|
|
|
|
|
```java |
|
|
|
|
-keep public class * extends android.app.Activity |
|
|
|
|
-keep public class * extends android.app.Application |
|
|
|
|
-keep public class * extends android.app.Service |
|
|
|
|
-keep public class * extends android.content.BroadcastReceiver |
|
|
|
|
-keep public class * extends android.content.ContentProvider |
|
|
|
|
-keep public class * extends android.view.View |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
事实上,我们完全可以把非 exported 的四大组件以及 View 混淆,但是需要完成 XML 替换和代码替换。 |
|
|
|
|
|
|
|
|
|
饿了么曾经开源了一个可以实现四大组件和 View 混淆的组件:[Mess](https://github.com/eleme/Mess) |
|
|
|
|
|
|
|
|
|
Android Studio 3.0 推出了新的 Dex 编译器 D8,在 3.1 及其以上版本 D8 已经作为默认的 Dex 编译器,大约可以减少 3% 的 Dex 体积,所以赶紧升级 AS 吧。 |
|
|
|
|
|
|
|
|
|
##### 去掉 Debug 信息或者去掉行号 |
|
|
|
|
|
|
|
|
|
某个应用通过相同的 ProGuard 规则生成一个 Debug 包和 Release 包,其中 Debug 包的大小是 4MB,Release 包只有 3.5 MB。 |
|
|
|
|
|
|
|
|
|
既然它们 ProGuard 的混淆与优化的规则是一样的,那么它们之间的差异在哪里呢?那就是 DebugItem。 |
|
|
|
|
|
|
|
|
|
DebugItem 里面主要包含两种信息: |
|
|
|
|
|
|
|
|
|
- 调试的信息 |
|
|
|
|
|
|
|
|
|
函数的参数变量和所有的局部变量。 |
|
|
|
|
|
|
|
|
|
- 排查问题的信息 |
|
|
|
|
|
|
|
|
|
所有的指令集行号和源文件行号的对应关系。 |
|
|
|
|
|
|
|
|
|
事实上,在 ProGuard 配置中一般我们也会通过下面的方式保留行号信息: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
-keepattributes SourceFile, LineNumberTable |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
##### Dex 分包 |
|
|
|
|
|
|
|
|
|
当我们查看一个 Dex 文件时,不知道你是否会注意到 defines methods 和 references methods 的区别? |
|
|
|
|
|
|
|
|
|
![](https://i.loli.net/2019/03/28/5c9c8e1feafb7.jpg) |
|
|
|
|
|
|
|
|
|
define classes and methods 是指真正在这个 Dex 定义的类以及它们的方法,而 reference method 指的是 define methods 以及 define methods 引用到的方法。 |
|
|
|
|
|
|
|
|
|
因为跨 Dex 调用造成的这些冗余信息,它对我们 Dex 的大小会造成哪些影响呢? |
|
|
|
|
|
|
|
|
|
1. method id 爆表 |
|
|
|
|
|
|
|
|
|
我们都知道每个 Dex 的 method id 需要小于 65536,因为 method id 的大量冗余导致每个 Dex 真正可以放 Class 变少,这也就造成了最终编译的 Dex 数量增多。 |
|
|
|
|
|
|
|
|
|
2. 信息冗余 |
|
|
|
|
|
|
|
|
|
因为要记录跨 Dex 调用的方法的详细信息,所以还需要记录方法对应的类信息以及定义等,造成了信息冗余 |
|
|
|
|
|
|
|
|
|
我们希望 Dex 的有效率应该在 80% 以上,同时每个 Dex 的方法数都是满的,即分配了 65536 个方法。 |
|
|
|
|
|
|
|
|
|
```java |
|
|
|
|
Dex 信息有效率 = defind methods 数量 / reference methods 数量 |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
那如何提高 Dex 的信息有效率呢?关键在于我们需要将有调用关系的类和方法 分配到同一个 Dex 中,即减少跨 Dex 的调用情况。但是由于类的调用关系非常复杂,我们不太可能算出最优解,只能得到局部最优解。 |
|
|
|
|
|
|
|
|
|
这个优化其实可以用 ReDex 来做,后面会讲到。 |
|
|
|
|
|
|
|
|
|
在这里,我突然想到 implementation 和 api,我们知道 implementation 不允许依赖传递,而 api 是可以的,Google 建议我们使用 implementation,这样在构建项目的时候只需重新编译修改的模块,同时,在从 Dex 分包的角度来看,具有 api 模块依赖关系的最好能放在同一个 Dex 中,以减少跨 Dex 调用。 |
|
|
|
|
|
|
|
|
|
##### Dex 压缩 |
|
|
|
|
|
|
|
|
|
我们能不能把 Dex 合并然后再压缩呢?当然可以,对于 Dex 格式来说,XZ 的压缩率可以比 Zip 高 30% 左右。但是这套方案似乎也存在一些问题: |
|
|
|
|
|
|
|
|
|
1. 首次启动解压 |
|
|
|
|
2. ODEX 文件生成 |
|
|
|
|
|
|
|
|
|
当 Dex 非常多的时候会增加应用的安装时间,对于十个以上的 Dex,首次生成 ODEX 的时间可能就会达到分钟级别。 |
|
|
|
|
|
|
|
|
|
#### Native Library 优化 |
|
|
|
|
|
|
|
|
|
对于 Native Library,传统的优化方法可能就是去除 Debug 信息、使用 c++_shared 这些。那么我们还有没有更好的优化方法呢? |
|
|
|
|
|
|
|
|
|
##### Library 压缩 |
|
|
|
|
|
|
|
|
|
跟 Dex 压缩一样,Library 优化最有效果的方法也是使用 XZ 或者 7-Zip 压缩。 |
|
|
|
|
|
|
|
|
|
在默认的 lib 目录,我们只需要加载少数启动相关的 Library,其它 Library 我们可在首次启动时解压。对于 Library 格式来说,压缩率同样可以比 Zip 高 30% 左右,效果十分惊人。 |
|
|
|
|
|
|
|
|
|
和 Dex 压缩一样,压缩方案的主要缺点在于首次启动的时间,毕竟对于低端机来说,多线程的意义并不大,因此我们要在包体积和用户体验之间做好平衡。 |
|
|
|
|
|
|
|
|
|
##### Library 合并与裁剪 |
|
|
|
|
|
|
|
|
|
在 Android 4.3 之前,进程加载的 Library 数量是有限制的,在编译过程,我们可以自动将部分 Library 合并成一个。 |
|
|
|
|
|
|
|
|
|
#### 资源优化 |
|
|
|
|
|
|
|
|
|
在我们的安装包中,资源相关的文件具体有下面几个,它们都是我们需要优化的目标文件。 |
|
|
|
|
|
|
|
|
|
| 文件/目录 | 描述 | |
|
|
|
|
| ------------------- | ------------------------------------------------------------ | |
|
|
|
|
| res/ | 存储编译后的资源文件,例如 Drawble、Layout 等 | |
|
|
|
|
| assets/ | 应用程序的资源,应用程序可以使用 AssetManager 来检索该资源 | |
|
|
|
|
| META-INF/ | 该文件夹一般存放于已经签名的 APK 中,它包含了 APK 中所有文件的签名摘要等信息 | |
|
|
|
|
| resources.arsc | 编译后的二进制资源文件 | |
|
|
|
|
| AndroidManifest.xml | Android 的清单文件,用于描述应用程序的名称、版本、所需权限、注册的四大组件 | |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 包压缩 |
|
|
|
|
|
|
|
|
|
安装完 Redex 之后: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
ANDROID_SDK=/Users/omooo/Library/Android/sdk redex app-debug.apk -o app-resize.apk |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
#### 参考 |
|
|
|
|
|
|
|
|
|
[包体积优化(上):如何减少安装包大小?](https://time.geekbang.org/column/article/81202) |
|
|
|
|
|
|
|
|
|
[包体积优化(下):资源优化的进阶实践](https://time.geekbang.org/column/article/81483) |
|
|
|
|
|
|
|
|
|
[减少 APK 的大小,Android 官方这样说](https://juejin.im/entry/5824447ca0bb9f0058d9d281) |
|
|
|
|
|
|
|
|
|
[支付宝 App 构建优化解析:Android 包大小极致压缩](https://mp.weixin.qq.com/s/_gnT2kjqpfMFs0kqAg4Qig) |
|
|
|
|
|
|
|
|
|
[美团 Android App包瘦身优化实践](https://tech.meituan.com/2017/04/07/android-shrink-overall-solution.html) |