为什么需要做 Android 包体积优化
- 提高下载转化率,同类型的 App 包体积越小,用户下载等待的时间也会越短,所以下载转换成功率也就越高。
- 应用市场要求。比如Google Play 应用市场强制要求超过 100MB 的应用只能使用 APK 扩展文件方式上传。
- 包体积过大对 App 的性能会有影响。比如:Resource 资源、Library 以及 Dex 类加载都会占用应用的一部分内存。
优化资源文件
使用 Lint 查找未使用的资源文件并删除
在 Android 项目中,可能会遗留很多旧版本不需要的子类文件、比如图片、xml等。我们可以通过 鼠标右键 -> Refactor -> Remove Unused Resource -> preview 来看到有哪些资源文件未被使用。如下图所示:
需要注意的,Android Lint 不会分析 assets 文件夹下的资源,因为 assets 文件可以通过文件名直接访问,不需要通过具体的引用,Lint 无法判断资源是否被用到。
使用 isShrinkResources
arduino
buildTypes {// 生产/测试环境配置
release {// 生产环境
isMinifyEnabled = false //是否对代码进行混淆
isShrinkResources = false // 指定是否为此生成类型启用缩减资源。
...
}
}
isShrinkResources
是用来开启删除无用资源,也就是没有被引用的文件(经过实测 drawable,layout,实际并不是彻底删除,而是保留文件名,但是没有内容,等等),但是因为需要知道是否被引用所以需要配合isMinifyEnabled
一起使用,只有当两者都为true的时候才会起到真正的删除无效代码和无引用资源的目的。
去除重复资源
在大型的 App 项目中,一个 App 一般由多个团队开发,并会划分成很多不同的模块。因此模块之间的资源可能会重复,但是资源名会不同(不同模块一般会加上模块的前缀)。为了解决这个问题,可以在 Gradle 执行=执行 package${flavorName}Task 之前通过修改 Compiled Resources 来实现重复资源的去除。
具体看 这里
图片压缩
一般来说,1000行代码在APK中才会占用 5kb 的空间,因此对图片的压缩是收益最大的行为。常见的图片压缩操作有:
- 能使用 webp 格式,尽量使用 webp
- 对于无法使用 webp的图片,可以在 TinyPNG 上先压缩后使用
更多关于图片压缩的可以看 这里
资源文件后加载技术
如果你正在开发的App确实有很多图片显示需求,而且都是GIF图,无法应用WebP转换,也无法压缩,该怎么办呢?典型的例子就是聊天软件中的动画表情,它们大部分都是GIF动图,而且数量很庞大。解决方案就是资源后加载。它有两种实现方案:
- 将动画资源使用ZIP压缩,放在assets目录中。使用时,或首次启动App时,由程序代码操作释放它们到存储空间中,这样做可以缩小APK的体积。
- 即随用随加载。当App检测到或"猜测"用户可能要发送动画表情时,通过网络请求获取动画表情。这种方案比起第一种更加节省空间,但缺点是当网络状况欠佳时,资源加载速度会很慢。
目前主流是这两种方案结合在一起使用。首先,在APK内部以ZIP格式压缩一些常用的动画表情,放到assets目录中,随APK分发。然后,在适当的时机完成其他动画表情的下载。这样一来,既保证了用户体验,又达到了给APK瘦身的目的。
需要注意,解压缩时最好对内部的资源文件做 md5 检查,确保其完整性。
资源混淆
资源混淆可以将 资源路径混淆成单个资源的路径 ,它可以使冗余的资源路径变短,例如将 res/drawable/wechat 变为 r/d/a。
资源混淆工具有 AndResGuard
R Field 的内联优化
android 中的 R 文件,除了 styleable 类型外,所有字段都是 int 型变量/常量,且在运行期间都不会改变。所以可以在编译时,记录 R 中所有字段名称及对应值,然后利用 ASM 工具遍历所有 Class,将除 R$styleable.class 以外的所有 R.class 删除掉,并且在引用的地方替换成对应的常量,从而达到缩减包大小和减少 Dex 个数的效果。
实现的插件有:ByteX
资源文件最少化配置
我们可以通过 resConfig 来配置使用哪些语言,从而让构建工具移除指定语言之外的所有资源。代码示例如下:
erlang
android
...
defaultConfig {
...
resConfigs "zh", "zh-rCN"
resConfigs "nodpi", "hdpi"
}
...
}
也可以利用 Density Splits 来选择应用应兼容的屏幕尺寸大小,代码示例如下:
bash
android {
...
splits {
density {
enable true
exclude "ldpi", "tvdpi", "xxxhdpi"
compatibleScreens 'small', 'normal', 'large', 'xlarge'
}
}
...
}
还可以通过多渠道打包,只打包所需要的依赖项,代码示例如下:
arduino
"huaweiImplementation"("com.huawei.hms:push:6.11.0.300")
其他资源优化建议
- 尽量每张图片只保留一份
比如说,我们统一只把图片放到 xhdpi 这个目录下,那么 在不同的分辨率下它会做自动的适配 ,即 等比例地拉伸或者是缩小。
- 资源在线化
我们可以 将一些图片资源放在服务器 ,然后 结合图片预加载 的技术手段,这些 既可以满足产品的需要,同时可以减小包大小。
- 统一应用风格
如设定统一的 字体、尺寸、颜色和按钮按压效果、分割线 shape、selector 背景 等等。
代码优化
代码混淆
代码混淆可以通过缩短变量和函数名以及丢失部分无用信息等方式,能使得应用包体积减小。代码示例如下:
arduino
android {
// ... 其他配置
buildTypes {
release {
// 开启代码混淆
isMinifyEnabled = false
// 指定混淆规则文件
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}
}
除了开启代码混淆外,isMinifyEnabled 还会从应用及其库依赖项中检测并安全地移除不使用的类、字段、方法和属性
Dex 分包优化
当我们的 APK 过大时,Dex 的方法数就会超过65536个,因此,必须采用 mutildex 进行分包。但是此时每一个 Dex 可能会调用到其它 Dex 中的方法,这种 跨 Dex 调用的方式会造成许多冗余信息,主要造成以下问题:
- 多余的 method id:
- 其它跨dex调用造成的信息冗余:
我们可以使用 redex 来分析了类之间的调用关系,尽量确保将有调用关系的类和方法分配到同一个 Dex 中。
使用 XZ Utils 进行 Dex 压缩
XZ Utils 是具有高压缩率的免费通用数据压缩软件。相对于典型的压缩文件而言,XZ Utils 的输出比 gzip 小 30%,比 bzip2 小 15%。
在 FaceBook 的 App 中就使用了 Dex 压缩 的方式,而且它 将 Dex 压缩后的文件都放在了 assets 目录中。而 apk 中的 Dex 仅包含了启动时要用到的类,这样可以为 Dex 压缩文件 secondary.dex.jar.xzs 的解压争取时间。
三方库处理
- 将图片加载库、网络库、数据库以及其他基础库进行统一,去掉冗余的库
- 尽可能地选择那些比较小的库来实现相同的功能
- 如果我们引入三方库的时候,可以 只引入部分需要的代码,而不是将整个包的代码都引入进来。如果你引入的三方库 没有进行过结构剥离,就需要 修改源码,只提取出来你需要的功能即可。
so 库优化
设置 abiFiliters
less
defaultConfig {
ndk {
// 指定一个或多个你需要的 ABI
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a"))
}
}
通过 abiFilters 设置支持的 abi。
So 库动态下发
将部分 So 文件使用动态下发的形式进行加载,也就是在业务代码操作之前,我们可以先从服务器下载下来 So,接下来再使用。
工具
android-classshark
android-classshark 是一个 面向 Android 开发人员的独立二进制检查工具 ,它可以 浏览任何的 Android 可执行文件,并且检查出信息,比如类的接口、成员变量等等。它还可以支持多种格式,比如说 APK、Jar、Class、So 以及所有的 Android 二进制文件如清单文件等。使用android-classshark显示方法数,如下图所示:
coverage
coverage 用于线上无用代码分析。
由于代码设计不合理以及keep规则限制等原因,静态代码检查无法找出所有的无用代码。我们可以从用户的角度去分析,对每个类插桩,执行时将信息上报到服务器。基于大量用户上报,用户没有用到的类可以被定义为无用类。在抖音项目中,就发现了1/6的无用类,不包含其引用的资源,共计3M(dex大小20M),如果能全部删除,将减少5%包大小。
redex
redex是一款安卓字节码(dex)优化程序,最初由 Facebook 开发。它提供了一个用于读取、写入和分析 .dex 文件的框架,以及一套使用该框架改进字节码的优化程序。经过 ReDex 优化的 APK 应该比源代码更小更快