前言
探索如何压缩一个 Android App 的包体积
Hello World
原始配置
首先是创建一个 Hello World App,只有一个 Activity 的 App 有多大。
创建 v1.0.1 的一个 Hello World App。没有多余的配置,只有 release 的 key 。
build.gradle 配置细节
1 | apply plugin: 'com.android.application' |
分别打出 debug 和 release 包。
debug
1
2
3
4Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2020/5/6 21:12 2506450 app-debug.apk
-a---- 2020/5/6 21:12 239 output.jsonrelease
1
2
3
4Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2020/5/6 21:08 2117330 app-release.apk
-a---- 2020/5/6 21:08 247 output.json
看一下 release 包的内容
可以看到主要还是 classes.dex 和 res 文件夹(资源文件)占用较多。其他如 kotlin 、META-INF 均为中间生成的文件,无法简单的进行优化。再有 AndroidManifest 文件更是无从优化。下面就从最容易上手的步骤开始逐步优化。
首先是进行代码混淆和资源的压缩。
配置混淆、资源压缩等
1 | buildTypes { |
完成上述配置后再次打 release 包
1 | Mode LastWriteTime Length Name |
看一下 release 包的内容
对比之前的截图,可以看到经过混淆的处理,classes.dex 整体压缩比非常高,从 1.3 MB 讲到了 332 kb。但是 res 文件夹内的资源文件并没有显著的效果,下面看看这部分是否有压缩空间。
代码 v1.0.3
为了方便后面的分析,这里了解一下 Apk 包中各类文件的用处。
Apk 包分析
我们都知道,Android 项目最终会编译成一个 .apk 后缀的文件,实际上它就是一个压缩包,和我们常接触的xxx.zip 以及 xxx.rar 本质上没有区别。因此,它内部还有很多不同类型的文件,这些文件,按照大小,共分为如下四类:
代码相关:classes.dex,我们在项目中所编写的 java 文件,经过编译之后会生成一个 .class 文件,而这些所有的 .class 文件呢,它最终会经过 dx 工具编译生成一个 classes.dex。
资源相关:res、assets、编译后的二进制资源文件 resources.arsc 和 清单文件 等等。res 和 assets 的不同在于 res 目录下的文件会在 .R 文件中生成对应的资源 ID,而 assets 不会自动生成对应的 ID,而是通过 AssetManager 类的接口来获取。此外,每当在 res 文件夹下放一个文件时,aapt 就会自动生成对应的 id 并保存在 .R 文件中,但 .R 文件仅仅只是保证编译程序不会报错,实际上在应用运行时,系统会根据 ID 寻找对应的资源路径,而 resources.arsc 文件就是用来记录这些 ID 和 资源文件位置对应关系 的文件。
So 相关:lib 目录下的文件,这块文件的优化空间其实非常大。
还有 META-INF,它存放了应用的 签名信息,其中主要有 3个文件,如下所示
MANIFEST.MF:其中每一个资源文件都有一个对应的 SHA-256-Digest(SHA1) 签名,MANIFEST.MF 文件的 SHA256(SHA1) 经过 base64 编码的结果即为 CERT.SF 中的 SHA256(SHA1)-Digest-Manifest 值。
CERT.SF:除了开头处定义的 SHA256(SHA1)-Digest-Manifest 值,后面几项的值是对 MANIFEST.MF 文件中的每项再次 SHA256(SHA1) 经过 base64 编码后的值。
CERT.RSA:其中包含了公钥、加密算法等信息。首先,对前一步生成的 CERT.SF 使用了 SHA256(SHA1)生成了数字摘要并使用了 RSA 加密,接着,利用了开发者私钥进行签名。然后,在安装时使用公钥解密。最后,将其与未加密的摘要信息(MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被修改。
再回过头看一下 release 刚才生成的包内资源相关的详细内容。
res 内为了适配各类设备,包含各种类型的资源,比如 xxxh,xxh, v17,v4,v20 甚至是 watch 的适配。还会包含多语言的一些资源,大部分情况下,是不需要这么繁杂的适配的。因此,可以做如下配置
1 | android { |
这样只会保留中文资源和 xxhdpi 的资源,当然,如果某个资源只在 xxxhdpi 下存在的话,那么也会智能的进行保留。
完成上述配置后,再次进行打包。
1 | Mode LastWriteTime Length Name |
可以看到 apk 的大小已经来到了 1 MB之内,只有 865 kb 了。关于 APK Size 和 Download Size 的区别,可以点击链接了解一下。总的来说,对国内用户似乎没有什么差异。
通过配置去除国家化和单一分辨率,res 减少了 33%,同时使的 resources.arsc 文件减小了将近一半的大小。
小结
至此,已经完成了一个基础项目的最小化的常规操作。865 kb 是最小值了吗?其实不然,res 文件夹下仍有可以优化的空间,同时可以通过自定义打包流程,用自己的算法压缩 apk ,或者用第三方工具对 dex 文件进行更精细化的处理。
其实,控制包体积是一个长期的任务,因为随着业务代码的增加,包体积一定会一天天的增长,因此需要在版本迭代的过程中不断就引入的资源、三方库和自己写的代码,进行分析、规划和记录,在恰当的时候删除冗余的代码和资源,同时也要有节制的进行代码的添加(这里主要还是三方库的引入和资源文件的添加)