盒子
盒子
文章目录
  1. 理解APK的结构
  2. 减少资源个数和尺寸
    1. 移除不使用的资源
    2. 最小化第三方库中资源的使用
    3. 只支持部分屏幕密度
    4. 减少动画帧数
    5. 使用Drawable对象
    6. 重用资源
    7. 通过代码绘制
    8. 压缩PNG文件
    9. 压缩PNG和JPEG文件
    10. 使用WebP文件格式
    11. 使用向量图
  3. 减少Native和Java代码
    1. 减少不必要的生成代码
    2. 移除枚举
    3. 减少Native库的大小
      1. 移除调试符号
      2. 避免抽取Native库
  4. 维持多个小的APK包

减少APK大小

本文转载于Bugly。

本文是对 Reduce APK Size 的翻译。

用户经常会避免下载看起来体积较大的应用,特别是在不稳定的2G、3G网络或者在以字节付费的网络。这篇文章描述了怎样减少你的APK大小,这会让更多的用户愿意下载你的应用。

理解APK的结构

在讨论怎样减少应用大小之前,先了解APK的结构是有用的。一个APK文件就是ZIP包,其中包含了组成你的应用的所有文件,比如Java类文件,资源文件,和一个包含被编译资源的文件。

一个APK包含了以下目录:

  • META-INF/: 包含CERT.SF和CERT.RSA签名文件,也包含了MANIFEST.MF文件。(译注:校验这个APK是否被人改动过)
  • assets/: 包含了应用的资源,这些资源能够通过AssetManager对象获得。
  • res/: 包含了没被被编译到resources.arsc的资源。
  • lib/: 包含了针对处理器层面的被编译的代码。这个目录针对每个平台类型都有一个子目录,比如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64和mips。

一个APK也包含了以下文件,其中只有AndroidManifest.xml是强制的:

  • resources.arsc: 包含了被编译的资源。该文件包含了res/values目录的所有配置的XML内容。打包工具将XML内容编译成二进制形式并压缩。这些内容包含了语言字符串和styles,还包含了那些内容虽然不直接存储在resources.arsc文件中,但是给定了该内容的路径,比如布局文件和图片。
  • classes.dex: 包含了能被Dalvik/Art虚拟机理解的DEX文件格式的类。
  • AndroidManifest.xml: 包含了主要的Android配置文件。这个文件列出了应用名称、版本、访问权限、引用的库文件。该文件使用二进制XML格式存储。(译注:该文件还能看到应用的minSdkVersion, targetSdkVersion等信息)

译注:使用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对象

一些图片不需要静态的图片资源,框架能在运行时动态地绘制图像。Drawable对象(XML的<shape>)只需要占用APK中的一点空间。另外,XML形式的Drawable对象能够产生遵循Material Design设计规范的图像。

重用资源

你能包含一张图片的很多变种,比如染色、阴影、旋转的版本。但是,我们推荐在运行时复用一张图片来定制化他们。

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中存储图像文件。

压缩PNG文件

AAPT工具能够在构建过程中通过无损压缩优化res/drawable/中的图片资源。比如aapt工具能将需要颜色少于256色的PNG变为8位PNG图,这样能够在保证图片质量的同时减少内存使用。

需要注意aapt有以下局限性:

  • aapt工具不会压缩asset目录的PNG文件。
  • 通过aapt的优化,图片文件会使用少于256色。
  • aapt工具可能会影响已经被压缩过的PNG文件。为了防止这种情况,你可以在gradle文件中设置cruncherEnabled为false禁用aapt对PNG的压缩。
aaptOptions {
cruncherEnabled = false
}

译注:建议把cruncherEnabled设为false,然后通过tinypng手工压缩PNG图片。

压缩PNG和JPEG文件

你能使用一些工具(比如pngcrush, pngquant, zopflipng)在不降低图像质量的前提下减少PNG文件大小。所有这些工具都能保留图像质量的情况下减少PNG文件大小。

pngcrush工具特别有效:这个工具通过迭代png过滤器和zlib参数,使用每种过滤器和参数的组合压缩图像,并选择最小的那个作为最后的输出。

对于JPEG文件,能使用packJPG压缩JPEG文件。

译注:guetzli是Google最近推出的JPEG编码器,官方宣称相同图片质量时,比libjpeg生成的图片小20-30%。

使用WebP文件格式

你也能使用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

减少Native和Java代码

有许多方法能够减少Java和Native的代码量。

减少不必要的生成代码

确保理解任何自动生成的代码。比如,许多protocol buffer工具生成了过多的方法和类,这会让你的应用大小翻倍。

移除枚举

一个枚举能让classes.dex文件增加1-1.4K。枚举的加入会快速增加应用体积。我们可以使用@IntDef注解和Proguard代替枚举,它能提供和枚举一样的类型安全转换。

减少Native库的大小

如果你的应用使用了Native代码和Android NDK,你也能通过优化代码减少应用体积,这里介绍的两个技巧是删除调试符号和避免抽取Native库。

移除调试符号

如果应用在开发中并且仍需要调试,那么我们能理解使用调试符号。使用Android NDK提供的arm-eabi-strip工具,能从Native库中删除不必要的调试符号,之后你再编译release包。

避免抽取Native库

在APK中存储未压缩的so文件,并且在Manifest文件的<application>中设置android:extractNativeLibs为false,这会防止在安装时PackageManager将APK中的so文件拷贝到文件系统,避免这种拷贝会让应用在做增量更新时的更新包更小。

维持多个小的APK包

你的APK会包含用户下载了但从未使用的内容,比如地区或语言信息(译注:比如我是中国人,我就不会用到其他语种的资源)。为了给用户创建小的下载包,你能把你的应用拆分成多个APK,这些APK的差别在于一些因素(比如屏幕大小或者GPU纹理支持)。

当一个用户下载了应用,设备根据自身的特性和设置获取正确的APK。这种方式能够让设备不获取设备不需要的资源。比如,如果设备是hdpi的,那么他就不需要xxxhdpi的资源。

更多信息请看Configure APK SplitsMaintaining Multiple APKs

支持一下
扫一扫,支持xiazdong