本文来自 "天天 P 图攻城狮" 公众号(ttpic_dev)
本文是对 Google 官方文档 Reduce APK Size 的翻译,点击 "阅读原文" 可以查看英文原文。
译者简介:damonxia(夏正冬),天天 P 图 Android 工程师
用户经常会避免下载看起来体积较大的应用,特别是在不稳定的 2G、3G 网络或者在以字节付费的网络。这篇文章描述了怎样减少你的 APK 大小,这会让更多的用户愿意下载你的应用。
在讨论怎样减少应用大小之前,先了解 APK 的结构是有用的。一个 APK 文件就是 ZIP 包,其中包含了组成你的应用的所有文件,比如 Java 类文件,资源文件,和一个包含被编译资源的文件。
一个 APK 包含了以下目录:
: 包含 CERT.SF 和 CERT.RSA 签名文件,也包含了 MANIFEST.MF 文件。(译注:校验这个 APK 是否被人改动过)
- META-INF/
: 包含了应用的资源,这些资源能够通过 AssetManager 对象获得。
- assets/
: 包含了没被被编译到 resources.arsc 的资源。
- res/
: 包含了针对处理器层面的被编译的代码。这个目录针对每个平台类型都有一个子目录,比如 armeabi, armeabi-v7a, arm64-v8a, x86, x86_64 和 mips。
- lib/
一个 APK 也包含了以下文件,其中只有 AndroidManifest.xml 是强制的:
: 包含了被编译的资源。该文件包含了
- resources.arsc
目录的所有配置的 XML 内容。打包工具将 XML 内容编译成二进制形式并压缩。这些内容包含了语言字符串和 styles,还包含了那些内容虽然不直接存储在 resources.arsc 文件中,但是给定了该内容的路径,比如布局文件和图片。
- res/values
: 包含了能被 Dalvik/Art 虚拟机理解的 DEX 文件格式的类。
- classes.dex
: 包含了主要的 Android 配置文件。这个文件列出了应用名称、版本、访问权限、引用的库文件。该文件使用二进制 XML 格式存储。(译注:该文件还能看到应用的 minSdkVersion, targetSdkVersion 等信息)
- AndroidManifest.xml
译注:使用 APK Analyzer 能够清晰地看出以上文件的内容,具体请看: 使用 APK Analyzer 分析你的 APK 。
APK 的大小会影响应用加载的速度,使用的内存大小,消耗的电量大小。一个最简单的缩小 APK 大小的方式是减少资源的个数和大小。特别地,你能移除应用中不再使用的资源,你也能使用可缩放的 Drawable 对象代替图片文件。这节讨论一些通过减少资源从而减少 APK 大小的方法。
译注:减少资源个数和缩小资源大小的效果是很显著的,比如有一天发现我组里的项目中还包含了旧版本的引导页视频(1.5M),一下就就减少了 1.5M,想想为了减少 1.5M 你得删多少代码才能办到。
lint 是 Android Studio 中的一个静态代码分析工具,检测在 "res/" 目录中你的代码没有引用的资源。当 lint 工具发现了项目中潜在的未使用的资源,它会打印以下类似信息:
- res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources]
注意:lint 工具不会扫描 "asset/" 目录,这个目录是通过反射引用的资源,或者链接到应用中的库文件。还有,lint 不会移除资源,只会发出警告。
被引用的库中可能会包含没使用的资源。如果你在 build.gradle 文件中启用 shrinkResources,则 Gradle 能自动移除这些资源。
为了使用 shrinkResources,你必须要启用代码混淆。在构建过程中,首先 proguard 移除了未使用的代码,然后 gradle 移除未使用的资源。
译注:lint 工具还能够检查出未使用的类、类中未使用的方法或变量。
更多关于通过代码混淆和其他方式减包,请看 Shrink Your Code and Resources。
在 Gradle 插件 0.7 或更高版本,你能申明应用支持的配置。Gradle 通过传递 resConfigs 和 defaultConfig 给构建系统,构建系统会防止不支持的配置出现在 APK 中,从而减少 APK 大小。更多信息请看 Remove unused alternative resources。
译注:在 hello world 工程里,resConfigs 配置为 "zh" 和不配置 resConfigs,resources.arsc 文件相差了 80K。
当开发 Android 应用时,你经常使用第三方库提升应用的可用性和灵活性。比如,你引用 Android Support Library 提升旧设备的用户体验,或者使用 Google Play 服务实现文字自动翻译。
如果一个第三方库原本是为服务器或普通电脑设计,会引入许多不需要的对象和方法。为了只引入应用需要的库中的那部分,你可以编辑库文件(如果库的 license 允许你这么做)。你也能使用另外的针对手机的实现同样功能的库。
注意:代码混淆能清除库中不被使用的代码,但是他不能移除库的大量内部依赖。
Android 支持很多设备集,其中包含了各种不同的屏幕密度。在 Android 4.4 及更高版本,框架支持不同的密度:ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi 和 xxxhdpi。尽管 Android 支持所有这些屏幕密度,但你不需要为每个密度都配置相应的资源。
如果你知道某种特定屏幕密度已经很少有用户使用了,那么你可以考虑是否需要为这个屏幕密度配置资源。如果你不包含针对特定屏幕密度的资源,那么 Android 会自动缩放原本针对其他密度的已有资源。
如果你的应用只需要缩放的图片,你甚至可以把图片存放在 drawable-nodpi 目录,从而节省更多空间。我们推荐每个应用都应该至少包含 xxhdpi 的图片。
更多关于屏幕密度的信息,请看 Screen Sizes and Densities。
使用帧动画会大大增加 APK 的大小。图 1 显示了目录中构成帧动画的多个 PNG 文件。每个图片都是动画的一帧。
对于加入动画的每帧,你都增加了 APK 中图片的个数。图 1 中,帧动画的帧率是 30 FPS。如果帧率降到 15 FPS,图片数量将减少一半。
图 1:帧动画的每一帧图片。
译注:还有一个常见的减包方案是删除帧动画中重复的图片资源,比如第 1 帧和第 3 帧的图片一样,那么只保留一个。
一些图片不需要静态的图片资源,框架能在运行时动态地绘制图像。Drawable 对象(XML 的
你能包含一张图片的很多变种,比如染色、阴影、旋转的版本。但是,我们推荐在运行时复用一张图片来定制化他们。
Android 提供了很多方式改变资源的颜色。对于 Android 5.0 及以上,使用 android:tint 和 tintMode 属性。对于更低版本,使用 ColorFilter 类。
你也能够删除那些只是对另一个资源做旋转的资源。下面的代码片段提供了对一个箭头旋转 180 度。
- <?xml version="1.0" encoding="utf-8"?><rotate xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_arrow_expand"
- android:fromDegrees="180"
- android:pivotX="50%"
- android:pivotY="50%"
- android:toDegrees="180" />
你也能通过代码绘制图像,从而减少 APK 大小。代码方式绘制图像不需要任何空间因为你不再需要在 APK 中存储图像文件。
AAPT 工具能够在构建过程中通过无损压缩优化 res/drawable / 中的图片资源。比如 aapt 工具能将需要颜色少于 256 色的 PNG 变为 8 位 PNG 图,这样能够在保证图片质量的同时减少内存使用。
需要注意 aapt 有以下局限性:
- aaptOptions {
- cruncherEnabled = false
- }
译注:建议把 cruncherEnabled 设为 false,然后通过 tinypng 手工压缩 PNG 图片。
你能使用一些工具(比如 pngcrush, pngquant, zopflipng)在不降低图像质量的前提下减少 PNG 文件大小。所有这些工具都能保留图像质量的情况下减少 PNG 文件大小。
pngcrush 工具特别有效:这个工具通过迭代 png 过滤器和 zlib 参数,使用每种过滤器和参数的组合压缩图像,并选择最小的那个作为最后的输出。
对于 JPEG 文件,能使用 packJPG 压缩 JPEG 文件。
译注:guetzli 是 Google 最近推出的 JPEG 编码器,官方宣称相同图片质量时,比 libjpeg 生成的图片小 20–30%。
你也能使用 WebP 文件格式存储图片而不是 PNG 或者 JPEG。WebP 格式是有损压缩(像 JPEG)且有透明通道(像 PNG),且压缩率高于 JPEG 或 PNG。
使用 WebP 文件格式也有一些缺点。第一,低于 Android 3.2 的版本不支持 WebP,第二,WebP 的解码时间比 PNG 长。
注意:Google Play 的 APK 的应用启动图标只能使用 PNG 格式,而不支持其他格式。
在 Android Studio 中,能将 BMP,JPG,PNG 或者静态 GIF 图片转换成 WebP 格式。更多信息,请看 Create WebP Images Using Android Studio。
你能使用向量图去创建一个分辨率无关的图标。使用向量图能够显著减少 APK 大小。在 Android 中向量图是以 VectorDrawable 对象形式存在的。使用 VectorDrawable 对象,一个 100B 的文件能生成一个屏幕大小的清晰图片。
但是,系统需要很长时间渲染 VectorDrawable 对象,更大的图片需要更长的时间显示在屏幕上。因此只有小图片才考虑使用向量图。
更多关于 VectorDrawable 对象的信息,请看 Working with Drawables。
有许多方法能够减少 Java 和 Native 的代码量。
确保理解任何自动生成的代码。比如,许多 protocol buffer 工具生成了过多的方法和类,这会让你的应用大小翻倍。
一个枚举能让 classes.dex 文件增加 1–1.4K。枚举的加入会快速增加应用体积。我们可以使用 @IntDef 注解和 Proguard 代替枚举,它能提供和枚举一样的类型安全转换。
如果你的应用使用了 Native 代码和 Android NDK,你也能通过优化代码减少应用体积,这里介绍的两个技巧是删除调试符号和避免抽取 Native 库。
如果应用在开发中并且仍需要调试,那么我们能理解使用调试符号。使用 Android NDK 提供的 arm-eabi-strip 工具,能从 Native 库中删除不必要的调试符号,之后你再编译 release 包。
在 APK 中存储未压缩的 so 文件,并且在 Manifest 文件的 中设置 android:extractNativeLibs 为 false,这会防止在安装时 PackageManager 将 APK 中的 so 文件拷贝到文件系统,避免这种拷贝会让应用在做增量更新时的更新包更小。
你的 APK 会包含用户下载了但从未使用的内容,比如地区或语言信息(译注:比如我是中国人,我就不会用到其他语种的资源)。为了给用户创建小的下载包,你能把你的应用拆分成多个 APK,这些 APK 的差别在于一些因素(比如屏幕大小或者 GPU 纹理支持)。
当一个用户下载了应用,设备根据自身的特性和设置获取正确的 APK。这种方式能够让设备不获取设备不需要的资源。比如,如果设备是 hdpi 的,那么他就不需要 xxxhdpi 的资源。
更多信息请看 Configure APK Splits 和 Maintaining Multiple APKs。
来源: http://www.tuicool.com/articles/fqeUVzB