用户通常会避免下载看起来太大的应用程序,尤其是在新兴市场,设备连接到断断续续的 2G 和 3G 网络,或使用有数据限制的计划。本页面介绍了如何减小应用程序的下载大小,这可以让更多用户下载您的应用程序。
使用 Android 应用捆绑包上传您的应用程序
将您的应用程序作为 Android 应用捆绑包 上传,以便在发布到 Google Play 时立即节省应用大小。Android 应用捆绑包是一种上传格式,包含应用程序的所有已编译代码和资源,但将 APK 生成和签名推迟到 Google Play。
然后,Google Play 的应用程序服务模型将使用您的应用捆绑包为每个用户的设备配置生成和提供优化的 APK,以便他们只下载运行您的应用程序所需的代码和资源。您无需构建、签名和管理多个 APK 来支持不同的设备,用户将获得更小、更优化的下载。
Google Play 对使用应用捆绑包发布的应用程序施加 压缩下载大小限制,为 200 MB。使用 Play 功能交付和 Play 资源交付可以实现更大的大小,但增加应用程序的大小会对安装成功率产生负面影响,并增加卸载率,因此我们建议您应用本页面中描述的指南,尽可能地减小应用程序的下载大小。
了解 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 参考
和 GitHub 上的示例。
仅支持特定密度
Android 支持不同的屏幕密度,例如以下密度
ldpi
mdpi
tvdpi
hdpi
xhdpi
xxhdpi
xxxhdpi
虽然 Android 支持上述密度,但您不需要将光栅化资产导出到每个密度。
如果您知道只有很小一部分用户拥有特定密度的设备,请考虑是否需要将这些密度捆绑到应用程序中。 如果您不包含特定屏幕密度的资源,Android 会自动缩放最初为其他屏幕密度设计的现有资源。
如果您的应用程序只需要缩放的图像,您可以将图像的单个变体放在 drawable-nodpi/
中,从而节省更多空间。 我们建议您在应用程序中至少包含一个 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 文件。- 要让
aapt
工具优化图像文件,图像文件需要使用 256 种或更少的颜色。 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 文件格式
除了使用 PNG 或 JPEG 文件外,您还可以使用 WebP 文件格式来存储图像。 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.4 KB 的大小。对于复杂的系统或共享库,这些增加会迅速累积。如果可能,请考虑使用 @IntDef
注解和 代码缩减 来去除枚举并将其转换为整数。这种类型转换保留了枚举的所有类型安全优势。
缩减原生二进制文件的大小
如果您的应用程序使用原生代码和 Android NDK,您还可以通过优化代码来缩减应用程序的发布版本的大小。两种有用的技术是删除调试符号和不提取原生库。
删除调试符号
如果您的应用程序正在开发中并且仍然需要调试,使用调试符号是有意义的。使用 Android NDK 中提供的 arm-eabi-strip
工具从原生库中删除不必要的调试符号。之后,您可以编译您的发布版本。
避免提取原生库
构建应用程序的发布版本时,通过在应用程序的 build.gradle.kts
文件中将 useLegacyPackaging
设置为 false
,在 APK 中打包未压缩的 .so
文件。禁用此标志可防止 PackageManager
在安装过程中从 APK 中将 .so
文件复制到文件系统。此方法使您的应用程序更新更小。
维护多个精简的 APK
您的 APK 可能包含用户下载但从未使用过的内容,例如额外的语言或每个屏幕密度的资源。为了帮助确保为用户提供最小的下载量,请将您的应用程序上传到 Google Play 使用 Android 应用程序包。上传应用程序包可以让 Google Play 为每个用户的设备配置生成和提供优化的 APK,因此他们只下载运行您的应用程序所需的代码和资源。您不必构建、签名和管理多个 APK 来支持不同的设备,用户可以获得更小、更优化的下载。
如果您没有将应用程序发布到 Google Play,您可以将应用程序细分为多个 APK,通过屏幕尺寸或 GPU 纹理支持等因素进行区分。
用户下载应用程序时,其设备会根据设备的功能和设置接收正确的 APK。这样,设备就不会接收设备没有的功能的资产。例如,如果用户拥有 hdpi
设备,他们不需要您可能包含的用于更高密度显示屏设备的 xxxhdpi
资源。
有关更多信息,请参阅 构建多个 APK 和 多个 APK 支持。