通常情况下,用户不会下载那些看起来太大的应用,尤其是在仍然使用稳定性欠佳的 2G 和 3G 网络或按流量计费的新兴市场。本页介绍如何缩减应用的下载大小,从而吸引更多用户下载。
使用 Android App Bundle 上传应用
以 Android App Bundle 格式上传应用,即可在将应用发布到 Google Play 时立即缩减应用大小。Android App Bundle 是一种上传格式,其中包含应用所有已编译的代码和资源,但 APK 生成及签名工作则交给 Google Play 来处理。
应用上传完毕后,Google Play 的应用服务模式会使用您的 app bundle,根据每位用户的设备配置,生成并提供经过优化的 APK,因此用户只需下载运行应用所需的代码和资源。您无需再构建、签署和管理多个 APK 即可支持不同的设备,而用户也可以获得更小、更优化的下载文件包。
Google Play 对使用 app bundle 发布的应用强制施加了不超过 200MB 的压缩下载大小限制。您可以使用 Play Feature Delivery 和 Play Asset Delivery 来增加应用大小,但增加应用大小可能会对安装成功率产生负面影响并增加卸载量,因此我们建议您遵循本页介绍的指南,尽可能缩减应用的下载大小。
了解 APK 结构
在讨论如何缩减应用的大小之前,有必要先介绍一下应用 APK 的结构。APK 文件由一个 Zip 压缩文件组成,其中包含构成应用的所有文件。这些文件包括 Java 类文件、资源文件和包含已编译资源的文件。
APK 包含以下目录:
META-INF/
:包含CERT.SF
和CERT.RSA
签名文件,以及MANIFEST.MF
清单文件。assets/
:包含应用的资源;应用可以使用AssetManager
对象检索这些资源。res/
:包含未编译到resources.arsc
中的资源。lib/
:包含特定于处理器软件层的已编译代码。此目录包含每种平台类型的子目录,如armeabi
、armeabi-v7a
、arm64-v8a
、x86
、x86_64
和mips
。
APK 还包含以下文件。只有 AndroidManifest.xml
是必需的:
resources.arsc
:包含已编译的资源。此文件包含res/values/
文件夹的所有配置中的 XML 内容。打包工具会提取此 XML 内容,将其编译为二进制文件形式,并压缩内容。此内容包括语言字符串和样式,以及未直接包含在resources.arsc
文件中的内容(例如布局文件和图片)的路径。classes.dex
:包含以 Dalvik 或 ART 虚拟机可理解的 DEX 文件格式编译的类。AndroidManifest.xml
:包含核心 Android 清单文件。此文件列出了应用的名称、版本、访问权限和引用的库文件。此文件使用 Android 的二进制 XML 格式。
缩减资源数量和大小
APK 的大小会影响应用加载速度、使用的内存量以及消耗的电量。您可以通过减少 APK 包含的资源的数量和大小来缩减 APK 的大小。具体来说,您可以移除应用不再使用的资源,并且可以用可伸缩的 Drawable
对象取代图片文件。此部分将讨论上述这些方法,以及可减少应用中的资源以缩减 APK 的总体大小的其他方法。
移除未使用的资源
lint
工具是 Android Studio 中附带的静态代码分析器,能够检测出 res/
文件夹中未被代码引用的资源。lint
工具发现项目中有可能未使用的资源后,会显示一条消息,如下例所示。
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources]
您添加到代码的库中可能包含未使用的资源。如果您在应用的 build.gradle.kts
文件中启用了 shrinkResources
,则 Gradle 可以代表您自动移除资源。
Kotlin
android { // Other settings. buildTypes { getByName("release") { minifyEnabled = true shrinkResources = true proguardFiles(getDefaultProguardFile('proguard-android.txt'), "proguard-rules.pro") } } }
Groovy
android { // Other settings. buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
如需使用 shrinkResources
,请启用代码缩减。在构建流程中,R8 首先会移除未使用的代码。然后,Android Gradle 插件会移除未使用的资源。
如需详细了解代码和资源缩减以及 Android Studio 缩减 APK 大小的其他方式,请参阅压缩、混淆和优化应用。
在 Android Gradle 插件 7.0 及更高版本中,您可以声明应用支持的配置。Gradle 会使用 resourceConfigurations
变种和 defaultConfig
选项将这些信息传递给构建系统。随后,构建系统会阻止来自其他不受支持的配置的资源出现在 APK 中,从而缩减 APK 的大小。如需详细了解此功能,请参阅移除未使用的备用资源。
尽量减少库中的资源使用量
在开发 Android 应用时,您通常需要使用外部库来提高应用的易用性和多功能性。例如,您可以引用 AndroidX 来提升过往设备上的用户体验,也可以使用 Google Play 服务获取应用中文本的自动翻译。
如果库是为服务器或桌面设备设计的,则可能包含应用不需要的许多对象和方法。如需仅包含应用所需的库部分,您可以编辑库的文件(如果库许可允许您修改库)。您还可以使用其他适合移动设备的库为应用添加特定功能。
原生动画图片解码
在 Android 12(API 级别 31)中,NDK ImageDecoder
API 已扩展为对使用动画 GIF 和动画 WebP 文件格式的图片的所有帧和时间数据进行解码。
使用 ImageDecoder
(而非第三方库)可进一步缩减 APK 大小,并从未来的安全性和性能相关更新中受益。
如需详细了解 ImageDecoder
API,请参阅 API reference
和 GitHub 上的示例。
仅支持特定密度
Android 支持不同的屏幕密度,如下所示:
ldpi
mdpi
tvdpi
hdpi
xhdpi
xxhdpi
xxxhdpi
尽管 Android 支持以上所有密度,但您无需将光栅化资源导出为每个密度。
如果您知道只有一小部分用户拥有具有特定密度的设备,请考虑是否需要将这些密度捆绑到应用中。如果不添加用于特定屏幕密度的资源,Android 会自动扩缩最初为不同屏幕密度设计的现有资源。
如果应用仅需要扩缩后的图片,则可以通过在 drawable-nodpi/
中使用图片的单个变体来节省更多空间。建议在应用中添加至少 1 个 xxhdpi
图片变体。
如需详细了解屏幕密度,请参阅屏幕尺寸和密度。
使用可绘制对象
某些图片不需要静态图片资源;框架可以在运行时动态绘制图片。Drawable
对象(XML 中为 <shape>
)会占用 APK 中的少量空间。此外,XML Drawable
对象会生成符合 Material Design 准则的单色图片。
重复使用资源
您可以为图片的变体添加单独的资源,例如同一图片经过色调调整、阴影设置或旋转的版本。不过,建议重复使用同一组资源,并在运行时根据需要对其进行自定义。
Android 提供了一些可用于更改资源颜色的实用程序,每个实用程序使用 android:tint
和 tintMode
属性。
您还可以省略仅作为另一个资源的旋转等效项的资源。以下代码段提供了一个示例,展示了如何通过绕图片中心位置旋转 180 度,将“拇指向上”变为“拇指向下”:
<?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_thumb_up" android:pivotX="50%" android:pivotY="50%" android:fromDegrees="180" />
从代码进行渲染
您还可以通过按一定程序渲染图片来缩减 APK 大小。按一定程序渲染可以释放空间,因为您不再在 APK 中存储图片文件。
压缩 PNG 文件
aapt
工具可以在构建流程中通过无损压缩来优化放置在 res/drawable/
中的图片资源。例如,aapt
工具可以通过调色板将不需要超过 256 种颜色的真彩色 PNG 转换为 8 位 PNG。这样做可以生成质量相同但内存占用量更小的图片。
aapt
具有以下限制:
aapt
工具不会缩减asset/
文件夹中包含的 PNG 文件。- 图片文件需要使用 256 种或更少的颜色才可供
aapt
工具进行优化。 aapt
工具可能会膨胀已压缩的 PNG 文件。为避免出现这种情况,您可以使用isCrunchPngs
标志为 PNG 文件停用此过程:
Kotlin
buildTypes.all { isCrunchPngs = false }
Groovy
buildTypes.all { isCrunchPngs = false }
压缩 PNG 和 JPEG 文件
您可以使用 pngcrush、pngquant 或 zopflipng 等工具缩减 PNG 文件的大小,同时不损失画质。所有这些工具都可以缩减 PNG 文件的大小,同时保持肉眼感知的画质不变。
pngcrush
工具尤为有效:该工具会迭代 PNG 过滤器和 zlib (Deflate) 参数,使用过滤器和参数的每个组合来压缩图片。然后,它会选择可产生最小压缩输出的配置。
如需压缩 JPEG 文件,您可以使用 packJPG 和 guetzli 等工具。
使用 WebP 文件格式
您还可以使用 WebP 文件格式的图片(而不是使用 PNG 或 JPEG 文件)。WebP 格式可提供有损压缩以及透明度(如 JPG 和 PNG),不过与 JPEG 或 PNG 相比,这种格式可以提供更好的压缩效果。
您可以使用 Android Studio 将现有 BMP、JPG、PNG 或静态 GIF 图片转换为 WebP 格式。如需了解详情,请参阅创建 WebP 图片。
使用矢量图形
您可以使用矢量图形创建与分辨率无关的图标和其他可伸缩媒体。您可以使用这些图形来大幅减少 APK 占用的空间。矢量图片在 Android 中以 VectorDrawable
对象的形式表示。借助 VectorDrawable
对象,100 字节的文件可以生成与屏幕大小相同的清晰图片。
不过,系统渲染每个 VectorDrawable
对象需要明显多得多的时间,而较大的图片则需要更长的时间才能显示在屏幕上。因此,请考虑仅在显示小图片时使用这些矢量图形。
如需详细了解如何使用 VectorDrawable
对象,请参阅可绘制对象。
将矢量图形用于动画图片
请勿使用 AnimationDrawable
创建逐帧动画,因为这样做需要为动画的每个帧添加单独的位图文件,而这会大大增加 APK 的大小。
您应改为使用 AnimatedVectorDrawableCompat
创建动画矢量可绘制资源。
减少原生和 Java 代码
您可以使用以下方法来缩减应用中的 Java 和原生代码库的大小。
移除不必要的生成代码
请务必了解自动生成的任何代码所占用的空间。例如,许多协议缓冲区工具会生成过多的方法和类,这可能会使应用的大小增加一倍或两倍。
避免使用枚举
单个枚举会使应用的 classes.dex
文件增加大约 1.0 到 1.4KB。这些增加的大小会快速累积,产生复杂的系统或共享库。如果可能,请考虑使用 @IntDef
注解和代码缩减移除枚举并将它们转换为整数。此类型转换可保留枚举的各种安全优势。
缩减原生二进制文件的大小
如果您的应用使用原生代码和 Android NDK,您还可以通过优化代码来缩减发布版应用的大小。移除调试符号和不提取原生库是两个实用方法。
移除调试符号
如果应用正在开发中且仍需要调试,则使用调试符号非常合适。您可以使用 Android NDK 中提供的 arm-eabi-strip
工具从原生库中移除不必要的调试符号。之后,您便可以编译发布 build。
避免解压缩原生库
在构建应用的发布版本时,请在应用的 build.gradle.kts
文件中将 useLegacyPackaging
设置为 false
,以便将未压缩的 .so
文件打包到 APK 中。停用此标志可防止 PackageManager
在安装过程中将 .so
文件从 APK 复制到文件系统,并可缩减应用更新的大小。
维护多个精简 APK
APK 可能包含用户下载但从不使用的内容,例如其他语言或针对特定屏幕密度的资源。如需确保为用户提供最小的下载文件,您应该使用 Android App Bundle 将应用上传到 Google Play。通过上传 App Bundle,Google Play 能够针对每位用户的设备配置生成并提供经过优化的 APK,因此用户只需下载运行您的应用所需的代码和资源。您无需再构建、签署和管理多个 APK 以支持不同的设备,而用户也可以获得更小、更优化的下载文件包。
如果您不打算将应用发布到 Google Play,则可以将应用分割为多个 APK,并按屏幕尺寸或 GPU 纹理支持等因素进行区分。
当用户下载您的应用时,其设备会根据设备的功能和设置接收正确的 APK。这样,设备不会接收设备所不具备的功能的资源。例如,如果用户具有 hdpi
设备,则不需要您可能会为具有更高密度显示器的设备提供的 xxxhdpi
资源。