目录:
一、为什么我们需要做 APK 的体积优化?
二、APK 组成
三、APK分析
四、代码体积优化
五、资源体积优化
六、so体积优化
七、其他方案
八、包体积监控
九、总结
一、为什么我们需要做 APK 的体积优化?
1、下载转化率
瘦身优化 最主要的好处是对应用 下载转化率 的影响,包体积越小,用户下载等待的时间也会越短,所以下载转换成功率也就越高。所以,安装包大小与下载转化率的关系 大致是成反比 的,即安装包越大,下载转换率就越小。现在很多大型的 App 一般都会有一个 Lite 版本的 App,这个也是出于下载转化率方面的考虑。
2、渠道合作商的要求
此外,还有一个原因,当我们的 App 做大之后,可能需要跟各个手机厂商合作预装,这些 渠道合作商会对你的 App 做详细的要求,只有达到相应的要求后才允许你的 App 预装到手机上。而且,越大的 App 其单价成本也会越高。所以,瘦身也是我们项目做大之后一定会遇到的一个问题。
3、体积过大对 App 性能的影响
- 安装时间:安装包越大,安装时间越长
- 运行时内存:Resource 资源、Library 以及 Dex 类加载都会占用应用的一部分内存。
- ROM 空间:安装包越大,安装后所占ROM空间越大。如果应用的安装包大小为 50MB,那么启动解压之后很可能就已经超过 100MB 了。
二、APK 组成
Android 项目最终会编译成一个 .apk 后缀的文件,实际上它就是一个 压缩包。因此,它内部还有很多不同类型的文件,这些文件,按照大小,共分为如下四类:
1、代码相关:
classes.dex,我们在项目中所编写的 java 文件,经过编译之后会生成一个 .class 文件,而这些所有的 .class 文件呢,它最终会经过 dx 工具编译生成一个 classes.dex。
2、资源相关:
res、assets、编译后的二进制资源文件 resources.arsc 和 清单文件 等等。res 和 assets 的不同在于 res 目录下的文件会在 .R 文件中生成对应的资源 ID,而 assets 不会自动生成对应的 ID,而是通过 AssetManager 类的接口来获取。此外,每当在 res 文件夹下放一个文件时,aapt 就会自动生成对应的 id 并保存在 .R 文件中,但 .R 文件仅仅只是保证编译程序不会报错,实际上在应用运行时,系统会根据 ID 寻找对应的资源路径,而 resources.arsc 文件就是用来记录这些 ID 和 资源文件位置对应关系 的文件。
3、So 相关:
lib 目录下的文件,这块文件的优化空间其实非常大。
4、META-INF:
它存放了应用的签名信息,不是我们的优化方向,不做具体介绍。
根据apk内的文件组成,我们可以得出我们的三大优化方向:代码(classes.dex)、资源(Assets、res、resources.arsc)、so库。
三、APK分析
1.Android Studio 自带的Analyze APK
Analyze APK 具有如下功能:
- 可以直观地查看到 APK 的组成,比如大小、占比等等。
- 查看 dex 文件的组成,可以看到dex声明多少个类,多少个方法。
- 对不同的 APK 进行对比分析。
2.反编译
- apktool工具:获取资源文件,提取图片文件,布局文件,还有一些XML的资源文件。
- dex2jar工具:将APK反编译成Java源码(将classes.dex转化为jar文件)。
- jd-gui工具:查看dex2jar中转换后的jar文件。
具体请参考:
Android APK反编译教程
四、代码体积优化
1.ProGuard
在 Android SDK 里面集成了一个工具 — Proguard,它是一个免费的 Java 类文件 压缩、优化、混淆、预先校验 的工具。它的主要作用大概可以概括为 两点,如下所示:
- 瘦身:它可以检测并移除未使用到的类、方法、字段以及指令、冗余代码,并能够对字节码进行深度优化。最后,它还会将类中的字段、方法、类的名称改成简短无意义的名字。
- 安全:增加代码被反编译的难度,一定程度上保证代码的安全。
使用请参考:
Android 混淆介绍
2.Android Studio Lint 查找无用的代码
ProGuard只是在编译期帮我们删除那些无用代码,在项目里面这些代码还是存在的。但是我们要更好地维护项目的话,一些无用的代码始终还是需要删除的。Lint工具就能够帮我们找到那些无用的代码,包括无用的类、方法和类属性。
如图,Code -> Inspect Code
直接点击确认
搜索结果如图所示,在Java的Unused declaration 和 Kotlin 的 Unused symbol 里面就可以找到那些没有用的类、方法和类属性。不过对于那些被反射调用的类、方法或属性是无法检测出来被调用的,这点需要注意一下。
3.三方库处理
将图片加载库、网络库、数据库以及其他基础库进行统一,去掉冗余的库。
同时,在选择第三方 SDK 的时候,我们可以将包大小作为选择的指标之一。在满足我们的需求和性能要求的情况下,我们应该尽可能地选择那些比较小的库来实现相同的功能。例如,对于图片加载功能来说,Picasso、Glide、Fresco 它们都可以实现,但是你引入 Fresco 之后会导致包大小增加很多,而 Picasso 却只增加了不到 100kb,所以引入不同的三方 SDK 对包大小的影响是不一样的。
如果我们引入三方库的时候,可以只引入部分需要的代码,而不是将整个包的代码都引入进来。很多库的代码结构都设计的比较好,比如 Fresco,它将图片加载的各个功能,如 webp、gif 功能进行了剥离,它们都处于单个的库当中。如果我们只需要 Fresco 的 webp 功能,那我们可以将除 webp 之外的别的库都给删掉,这样你引入的三方库就很小了,包大小就降下来了。如果你引入的三方库 没有进行过结构剥离,就需要 修改源码,只提取出来你需要的功能即可。
五、资源体积优化
1、冗余资源优化
使用 Lint 的 Remove Unused Resource
APK 的资源主要包括图片、XML,与冗余代码一样,它也可能遗留了很多旧版本当中使用而新版本中不使用的资源,这点在快速开发的 App 中更可能出现。我们可以通过点击右键,选中 Refactor,然后点击 Remove Unused Resource => preview 可以预览找到的无用资源,点击 Do Refactor 可以去除冗余资源。
需要注意的,Android Lint 不会分析 assets 文件夹下的资源,因为 assets 文件可以通过文件名直接访问,不需要通过具体的引用,Lint 无法判断资源是否被用到。
使用 shrinkResources true 来开启资源压缩
资源缩减只有在与代码缩减配合使用时才能发挥作用。在代码缩减器移除所有不使用的代码后,资源缩减器便可确定应用仍要使用的资源,当您添加包含资源的代码库时尤其如此。您必须移除不使用的库代码,使库资源变为未引用资源,因而可由资源缩减器移除。
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
2、移除未使用的备用资源
Gradle 资源缩减器只会移除未由应用代码引用的资源,这意味着,它不会移除用于不同设备配置的备用资源。如有必要,您可以使用 Android Gradle 插件的 resConfigs
属性移除应用不需要的备用资源文件。
以下代码段展示了如何设置只保留英语和法语的语言资源:
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
3、图片压缩
对于图片压缩,我们可以在 tinypng 这个网站进行图片压缩,但是如果 App 的图片过多,一个个压缩也是很麻烦的。因此,我们可以 使用 TinyPNG Image Optimizer 插件来对图片进行自动化批量压缩。TinyPNG Image Optimizer 插件一开始使用时需要填写 ApiKey, 申请入口如下 https://tinypng.com/developers
但是,需要注意的是,在 Android 的构建流程中,AAPT 会使用内置的压缩算法来优化 res/drawable/ 目录下的 PNG 图片,但这可能会导致本来已经优化过的图片体积变大,因此,可以通过在 build.gradle 中 设置 cruncherEnabled 来禁止 AAPT 来优化 PNG 图片,代码如下所示:
aaptOptions {
cruncherEnabled = false
}
4、使用针对性的图片格式
首先,如果能用 VectorDrawable 来表示的话,则优先使用 VectorDrawable;否则,看是否支持 WebP,支持则优先用 WebP;如果也不能使用 WebP,则优先使用 PNG,而 PNG 主要用在展示透明或者简单的图片,对于其它场景可以使用 JPG 格式。简单来说可以归结为如下套路:
VD(纯色icon)->WebP(非纯色icon)->Png(更好效果) ->jpg(若无alpha通道)
注意点:
- App的minSdkVersion高于14(Android 4.0+)的话,才可以选用WebP格式。
- 通过Android Studio 的 Vector Asset 可以把矢量图转换成 VectorDrawable,参考 Android Studio神器之Vector Asset
- 通过Android Studio 的 Conver to Webp 即可把png 转换成 webp格式的图片,使用方式:在Android Studio 中,把图片资源选中,点击鼠标右键即可看到 Conver to Webp的选项。
5、资源混淆t
目前使用较新的Gradle 版本时,发现打release包时资源已经进行了一定程度的混淆。大家可以直接更新到最新版本的Android Studio 版本和 gradle 版本来实现资源混淆。
AndResGuard目前已经很久没维护了,大家可以参考学习其原理。
6、尽量每张图片只保留一份
比如说,我们统一只把图片放到 xhdpi 这个目录下,那么 在不同的分辨率下它会做自动的适配,即 等比例地拉伸或者是缩小。
7、资源在线化
我们可以 将一些图片资源放在服务器,然后 结合图片预加载 的技术手段,这些 既可以满足产品的需要,同时可以减小包大小。
六、so体积优化
1、So 移除方案
So 是 Android 上的动态链接库,在我们 Android 应用开发过程中,有时候 Java 代码不能满足需求,比如一些 加解密算法或者音视频编解码功能,这个时候就必须要通过 C 或者是 C++ 来实现,之后生成 So 文件提供给 Java 层来调用,在生成 So 文件的时候就需要考虑生成市面上不同手机 CPU 架构的文件。目前,Android 一共 支持7种不同类型的 CPU 架构,比如常见的 armeabi、armeabi-v7a、X86 等等。理论上来说,对应架构的 CPU 它的执行效率是最高的,但是这样会导致 在 lib 目录下会多存放了各个平台架构的 So 文件,所以 App 的体积自然也就更大了。
因此,我们就需要对 lib 目录进行缩减,我们 在 build.gradle 中配置这个 abiFiliters 去设置 App 支持的 So 架构,其配置代码如下所示:
defaultConfig {
ndk {
abiFilters "armeabi"
}
}
一般情况下,应用都不需要用到 neon 指令集,我们只需留下 armeabi 目录就可以了。因为 armeabi 目录下的 So 可以兼容别的平台上的 So,相当于是一个万金油,都可以使用。但是,这样 别的平台使用时性能上就会有所损耗,失去了对特定平台的优化。
对于性能敏感的模块,它使用到的 So,我们都放在 armeabi 目录当中随着 Apk 发出去,然后我们在代码中来判断一下当前设备所属的 CPU 类型,根据不同设备 CPU 类型来加载对应架构的 So 文件。这里我们举一个小栗子,比如说我们 armeabi 目录下也加上了 armeabi-v7 对应的 So,然后我们就可以在代码当中做判断,如果你是 armeabi-v7 架构的手机,那我们就直接加载这个 So,以此达到最佳的性能,这样包体积其实也没有增加多少,同时也实现了高性能的目的,比如 微信和腾讯视频 App 里面就使用了这种方式。
七、其他方案
1、插件化
我们可以使用插件化的手段 对代码结构进行调整,如果我们 App 当中的每一个功能都是一个插件,并且都是可以从服务器下发下来的,那 App 的包体积肯定会小很多。
2、业务梳理
我们需要回顾过去的业务,合理地去评估并删除无用或者低价值的业务。简单来说就是删减无用或低价值的功能。
3、转变开发模式
如果所有的功能都不能移除,那就可能需要去转变开发模式,比如可以更多地 采用 H5、小程序 这样开发模式。
八、包体积监控
包体积的监控,主要可以从如下 三个纬度 来进行:
- 大小监控:通常是记录当前版本与上一个或几个版本直接的变化情况,如果当前版本体积增长较大,则需要分析具体原因,看是否有优化空间。通过Android Studio 自带的Analyze APK就可以看到报体积和各个组成部分的体积大小。
- 依赖监控:包括Jar、aar 依赖。
- 规则监控:我们可以把包体积的监控抽象为无用资源、大文件、重复文件、R 文件等这些规则。
包体积的 大小监控 和 依赖监控 都很容易实现,而要实现 规则监控 却得花不少功夫,推荐使用 Matrix 中的 ApkChecker 来实现包体积的规则监控。
九、总结
apk体积的优化方向主要是代码体积优化、资源体积优化和so体积优化,本文总结各部分的一些优化方式,都是比较基本的优化方式,能实现上面提到的点,我们的apk体积优化已经基本ok了。如果需要有需要更加深入的,可以参考下面所列出的本文所参考到的文章。
参考:
缩减、混淆处理和优化应用(官方文档)
Android包体积优化(常规、进阶、极致)
深入探索 Android 包体积优化(匠心制作-上)
深入探索 Android 包体积优化(匠心制作-下)