性能优化(6)-减小APK体积

96
ZJ_Rocky
2017.11.17 14:33* 字数 3554

主目录见:Android高级进阶知识(这是总目录索引)
[译]Reduce APK Size
 个人感觉这篇文章写得还是比较全的,所以这里就来翻译一下这篇文章,希望大家看了能有所收获。这篇是谷歌自己的文章,应该来说还是比较权威的,APK的体积变小意味着用户下载时候需要的流量也比较少,而且下载时间相对缩短,这是一个不错的用户体验。

一.目标

今天翻译这篇文章主要是为了大家了解有这么多可以缩小apk包体积的方法,在自己写代码的时候可以适当地注意,所以今天目标如下:
1.了解减小apk体积的若干方法;
2.能在实际场景中考虑和使用到里面的方法。

二.译文

 用户通常会避免下载大应用,特别是市场上的设备连接到2G和3G或者按字节流量付费。这篇文章重点描述了怎么减少你应用Apk的体积,让更多的用户能下载它。

1.理解APK结构

 在讨论怎么减小Apk体积之前,理解一个应用的APK结构是非常有帮助的。一个apk文件就是由一个zip压缩包组成,这个zip包含了所有组成你应用的文件。这些文件包含了java的字节码文件,资源文件和一个包含了编译后资源的文件。
 一个apk包含了如下的目录:

  • META-INF/: 包含了CERT.SFCERT.RSA签名文件,以及MANIFEST.MFmanifest 文件.
  • assets/:包含了应用的assets,应用可以通过 AssetManager对象来获取这些资源.
  • res/:包含了没有被编译成resources.arsc的所有资源.
  • lib/:包含了用于软件处理的编译后的代码,这个目录还包含了针对不同平台类型的子目录 armeabi, armeabi-v7a, arm64-v8a, x86, x86_64mips.

一个APK还包含了如下的文件,但是其中只有AndroidManifest.xml是必须的。

  • resources.arsc:包含了编译后的资源。这个文件包含了res/values/文件夹下面的所有XML文件内容,打包工具抽取了XML文件内容,并把它编译成二进制文件格式,并且进行压缩。该内容包含了language strings(语言相关的字符串)和styles,并且包括没有直接放在resources.arsc文件中的内容路径,比如layout文件以及图片文件.
  • classes.dex:包含了class文件编译成的dex文件,这是可以被Dalvik/ART虚拟机识别的文件格式。
  • AndroidManifest.xml:包含了Android核心manifest文件。这个文件罗列了应用的名字,版本,访问权限和引用的library库。该文件采用Android的二进制XML格式。

2.减小资源数量和体积

 你apk的体积直接影响了你程序的加载速度,占用内存大小,消耗的电量。一个简单有效的方法是减小apk包含的资源的数量和体积。特别是,你可以移除一些不再使用的资源,或者你可以使用一些可扩展的 Drawable对象来替代图片资源。这部分主要讨论这些方法以及另外可以减少资源占用的方法。

移除无用的资源

 使用Lint工具,这是一个Android Studio中的静态代码分析工具,可以检测 res/文件夹中没有被引用的资源(其实Lint工具不仅仅有这个功能)。当Lint检测到工程中潜在的不被使用的资源,它就会打印出如下消息:

res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources]

注意:Lint不会扫描assets/文件夹,assets资源是通过反射来引用的,或者应用中引用的其他library库。他只是给你一个警告,并不会帮助你移除这些无用的资源。

同时你添加到代码中的library库可能包含无用的资源,如果你在app里的build.gradle中启用了shrinkResources,那么Gradle 会自动帮你移除这些无用的资源。

 android {
    // Other settings

    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

为了使用shrinkResources,你必须开启 code shrinking。在build阶段,ProGuard会移除无用的代码但是会保留无用的资源。然后Gradle过后会移除无用的资源。

更多的关于ProGuard 和使用Android Studio 帮助你减少Apk体积的方法,参阅Shrink Your Code and Resources.

在Android Gradle Plugin 0.7或者更高的版本,你可以声明app支持的所有配置,Gradle会把resConfigresConfigsflavors 和defaultConfig等选项信息传递给构建系统。构建系统会防止一些不受支持的资源出现在你的apk中,从而减少你的apk体积。想要了解更多这方面的特性,参考Remove unused alternative resources

最小化Library库中资源使用

 在开发android应用的时候,你经常会使用外部的library库来提升你app的可用性和扩展性。例如,你会引入Android Support Library来提升用户在旧机型上面的体验,或者你会使用Google Play Services来自动翻译你app中的文本。
 如果一个library库被设计为一个服务或者桌面,那么它就会包含很多你应用没有用到的对象和方法。为了只包含你工程中用到的部分,你可以修改这个库文件,如果license许可的话,同时你也可以使用另外的移动端友好的库来给你的app增加特定的功能。
注意:ProGuard能清除导入库中一些无用的代码,但是不能清除库中大的内部依赖。

只支持特定的分辨率

 Android支持了非常多的不同分辨率的设备,在Android 4.4 (API level 19)或者更高版本,框架支持了不同的分辨率:ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpixxxhdpi,虽然Android支持了这些所有的分辨率,但是你并不需要导出所有栅格化的assets到各个分辨率。
 如果你知道只有一小部分用户使用特定分辨率的设备,请考虑是否需要支持这些分辨率。如果你没包含特定屏幕分辨率的资源,那么Android会自动缩放其他分辨率的资源来支持。
 如果你的app只需要缩放的图片,那么为了节省空间你可以使用单个版本在drawable-nodpi/中的图片,我们建议,每个应用都至少有一个xxhdpi版本的图片。
想要更多关于屏幕分辨率的信息,请参照Screen Sizes and Densities

使用drawable对象

 一些图片不需要一个静态图片资源:框架能在运行期动态绘制一张图片,Drawable对象(<shape> in XML)只在你的apk中占用非常小的控件。另外,XML Drawable对象能生成符合material design指南的单色图。

重用资源

 你可能会针对一张图片的不同变形而提供单独的资源,比如图像的着色(tinted),阴影(shaded)或者旋转(rotated)。我们建议就算这样,你可以重用这个资源,在运行期时候根据需要进行自定义。
 Android提供了不同的工具来改变一个asset的颜色,比如在Android 5.0 (API level 21) 或者更高版本上面使用android:tinttintMode属性 ,在低版本平台上则可以使用 ColorFilter类。
 你甚至可以忽略掉那些仅仅只是做了一个旋转变化的资源,以下的代码段就提供了一个以图片中心点为圆心旋转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的体积,程序渲染将会释放你的很多空间,因为你的程序中不会存储一张图片资源。

处理PNG文件

 在build阶段,aapt工具能利用无损压缩来优化放在res/drawable/中的图片资源。例如,aapt工具能利用颜色调色板(color palette)将不需要多于256种颜色的真彩色PNG转化成一个八位PNG。这样做会产生一个质量相同但是占用空间小的图片。
 但是要记住appt工具有如下的限制:

  • aapt工具不会压缩包含在asset/中的PNG文件
  • 图片文件需要256个或者更少的颜色来给aapt工具优化
  • aapt工具可能会使已经压缩过的PNG文件变大,为防止这种情况发生,你可以在Gradle中使用cruncherEnabled 标识来禁用PNG文件的处理。
aaptOptions {
    cruncherEnabled = false
}
压缩PNG和JPEG文件

 你能使用工具来减少PNG图片的体积但是不损失他的质量,例如: pngcrush,pngquant或者zopflipng.这些工具都可以减少PNG的体积但是不损失他的质量。

pngcrush工具是非常高效地:这个工具会迭代所有的PNG文件过滤器和zlib (Deflate) 参数。利用过滤器和参数的组合来压缩一张图片,然后他会选择产生最小压缩输出的配置。

压缩JPEG图片你可以利用 packJPGguetzli.

使用WebP文件格式

 在Android 3.2 (API level 13)或者更高版本上面,你同时可以使用WebP文件格式来替代你的PNG和JPEG文件。WebP格式提供了有损压缩(如JPEG)和透明度(如PNG),但是他能提供更好的压缩比。

 你可以用Android Studio来将BMP,JPG,PNG 或者静态GIF 图片转化为WebP格式,想要更多信息,请参考Create WebP Images Using Android Studio

注意:Google Play只接受launcher icons为PNG格式的apk。

使用矢量图片

 你可以使用矢量图片来创建分辨率独立的图标或者其他可伸缩媒体。使用这些图形可以大大减少你的apk体积,矢量图片可以在 VectorDrawable展示,使用VectorDrawable对象可以用100字节的文件就产生一张屏幕大小的文件。

但是不足的是,他需要大量时间来渲染VectorDrawable对象,如果图片更大,需要的时间可能会更多。所以在展示小图片的时候我们可以考虑使用矢量图片。

想要了解更多使用VectorDrawable对象的信息,请看Working with Drawables

使用矢量图来替换动画图片

 不用使用 AnimationDrawable来创建逐帧动画,因为这么做需要你为每一帧动画都包含一张bitmap图片,这样会大大增加你的apk体积。

作为替代,你可以使用 AnimatedVectorDrawableCompat来创建 animated vector drawables

3.减少Native和java的代码

这里有几种减少你Native和java代码库的方法:

减少不必要的生成的代码

 确保你能够理解任何自动生成的代码的部分。例如,一些protocol buffer工具能生成一些多余的方法和类,这无疑会是你的app体积变成两倍三倍。

避免使用枚举

一个独立的枚举能增加1.0到1.4KB的大小到你的classes.dex文件,对于一些复杂系统或者共享库可能增加的会更快。如果可能,你可以使用@IntDef注解和ProGuard来去掉枚举并将它们转化为整型。这种类型转换保留了枚举的所有类型安全的好处。

减少本地二进制文件的大小

如果你的程序使用native代码或者android NDK,那么你也可以优化你的代码来减少apk体积,两个有用的方式是删除debug标记,不提取本地库。

1.移除debug标记
如果你的应用在开发中并且需要调试,那么让你的debug 标记有意义。使用android NDK中提供的arm-eabi-strip工具来移除native库中不需要的debug标记。之后,再编译你的release版本。

2.避免抽取native库
.so文件未压缩存储于apk中,然后在你app manifest中的<application>设置android:extractNativeLibs标记为false,这样会防止 PackageManager在安装过程中从apk中拷贝出来.so文件,而且还会带来一个好处就是会使你的app差分更新变得更小。

保持多个精简版apk

你的apk可能会包含有用户下载了但是未使用到的内容,比如区域或者语言信息。为了给你用户创建最小化的下载,你可以将你的app分出多个apk,并且根据屏幕尺寸和GPU纹理支持等因素来细分。

当用户下载你的应用的时候,他的设备就会根据设备特征和配置来获取正确的apk。这样,设备不会接收设备没有的功能的资源。例如,用户有hdpi的设备,他们就不需要为更高分辨率设备准备的xxxhdpi资源。

想要更多信息的话,请参考 Configure APK SplitsMaintaining Multiple APKs

Android高级进阶
Web note ad 1