减小APK的大小
本文是笔者阅读Google官方文档,并尝试翻译和理解
用户一般会避免下载比较大的应用,特别是用户在使用2G和3G或者是一些按照字节收费的网络条件下时。这篇文章讲解了如何减小我们应用APK的大小,来让更多用户下载使用我们的应用。
理解APK的结构
在开始讨论如何减小app的大小前,我们有必要弄清楚APK的结构。说白了,一个APK就是一个压缩包,这个压缩包中包含了我们应用所需要的所有文件。这些文件包括Java类文件,资源文件,还有一个含有编译资源的文件
- 一个APK文件包含了以下目录:
- META-INF/: 包含了CERT.SF和CERT.RSA签名文件,还有MANIFEST.MF清单文件
- assets/:包含应用的资源,使用AssetManager访问
- res/:包含了没有被编译进resources.arsc文件的资源
- lib/:包含了对处理器层面编译的代码。这个目录包含了对每一个平台的子目录:例如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, and mips
- APK文件也包含以下这些文件,但只有AndroidManifest.xml是必须要有的
- resources.arsc:包含编译资源。这个文件包含所有res/values下的xml文件内容。打包工具提取xml内容,并且将他压缩编译成二进制文件。它包括语言字符串和样式,还有没有编译进resources.arsc文件的路径,比如布局文件和图片
- classes.dex:class类文件编译成classes.dex,而这种.dex格式是能被 Dalvik/ART 识别的
减少资源数量和大小
APK的大小对应用的读取速度,内存消耗和电量消耗都有影响。一个比较简单减少APK大小的方法就是,减少资源的数量和大小。我们可以删除不用的资源,使用可缩放的Drawable对象来代替图片文件。这节讨论几种通过降少资源文件来减小APK大小的方法
移除未使用的资源
Lint工具是集成在Android Studio一款静态代码分析器,它可以检测res/文件夹下没有被引用的资源。当Lint工具发现了潜在的没有被使用的资源时,它会打印如下信息:
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears
to be unused [UnusedResources]
注意:Lint工具不会扫描assets/文件夹,assets是通过反射或者是app所使用的类库来引用的
应用所使用的类库也可能包含包含未使用的资源。当我们在app的build.gradle文件中开启shrinkResources时,gradle可以自动移除这些资源
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
为了使用shrinkResources,你必须开启代码压缩。在应用构建过程中,首先ProGuard移除未使用的代码,然后gradle移除未使用的资源
更多关于通过代码混淆和其他方式减包,请看Shrink Your Code and Resources。
在Android gradle 0.7和更高的版本中,你可以指定应用支持的配置。Gradle会将这些配置通过 resConfig和resConfigs传递给构建系统。构建系统会避免应用不支持的配置出现在APK中,以达到减小APK大小的目的
最小化类库资源的使用
在开发安卓app时,会经常使用外部的类库来提高应用的可用性。例如,你可以引用Android Support Library提升应用的体验,或者可以使用Google Play Services获得文本的自动翻译服务
如果一个类库是为服务器或者桌面设计的,它可以包含很多我们应用中不需要的对象和方法。引用类库中你所需要的部分,如果有授权的话,你甚至可以根据自己的需求来需改类库。当然,你也可以使用专门为手机应用开发的类库
注意:ProGuard可以清理引用类库中不需要的的代码,但是不能移除比较大的类库
支持制定的屏幕密度
安卓支持各种屏幕密度的手机。在安卓4.4或者更高版本中,framework支持以下不同的密度:ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi and xxxhdpi。虽然,安卓支持众多屏幕密度,但是你没有必要为不同的屏幕密度都提供资源
如果你知道只有小部分的人使用某个屏幕密度,现在你就要考虑是否对它提供支持了。如果你不为指定屏幕密度的设备提供资源,安卓会自动的缩放已经存在为其他屏幕密度准备的资源
如果你的app只需要缩放的图片,你甚至可以通过只将图片放入drawable-nodpi/来节省更多的控件。我们建议app中至少要包括xxhdpi分辨率的图片资源
减少动画的帧数
帧动画会大大增加APK的体积。下图展示了一个帧动画所需要的每个图片,每个图片代表动画的一帧
动画的每一帧都在增大APK的体积,下图中的动画包含30帧,如果帧率减少到5帧,图片数量就减少一半
使用Drawable 对象
有些图片并不需要静态的图片资源;framework可以动态的画图。Drawable 对象(xml中的)只占应用很小的空间。XML的Drawable 对象画出符合 material design 设计指导的单色图片
重用资源
通常会在应用中使用一张图片的很多变种,例如彩色的、阴影的、或者旋转的。我们推荐在运行时,复用这一张图片定制他们。
安卓提供了很多方式来改变资源的颜色。在安卓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的大小,因为你不再需要空间来存储图片
压缩PNG图片
aapt工具可以在编译时无损的压缩res/drawable/下面的图片。例如,aapt工具可以将真彩PNG图片转换成一个8位的PNG。这样能保证图片质量的同时,减小图片的大小
- aapt工具有以下限制:
- asset/目录下PNG文件不会被压缩
- 通过aapt的优化,图片文件会使用少于256色。
- aapt工具可能会影响已经被压缩过的PNG文件。为了防止这种情况,你可以在gradle文件中设置cruncherEnabled为false禁用aapt对PNG的压缩。
aaptOptions {
cruncherEnabled = false
}
压缩PNG和JPEG文件
你可以通过使用pngcrush, pngquant, 或者 zopflipng工具无损的压缩PNG文件。
pngcrush工具特别有效:这个工具通过迭代png过滤器和zlib参数,使用每种过滤器和参数来压缩图片。并选择那个最小的作为最后的输出
你可以使用packJPG来压缩图片
使用WebP格式文件
除了使用PNG和JPEG文件,你也可以使用webp文件在图片。WebP格式是有损压缩(像JPEG)且有透明通道(像PNG),且压缩率高于JPEG或PNG。
然而,使用WebP文件有几个需要注意的缺点。首先,安卓3.2以下不支持webp文件。其次程序解析webp文件的事件比PNG文件长
注意:Google Paly 上的APK的启动图标只支持PNG,其它格式不支持
使用向量图
使用响亮图可以创建一个分辨率无关的图标或者其他可缩放的媒体文件。使用向量图可以大大降少应用体积。在Android中向量图是以VectorDrawable对象形式存在的。使用VectorDrawable对象,一个100B的文件能生成一个屏幕大小的清晰图片。
但是,程序对向量图渲染事件长。所以,当我们使用小图标时才考虑使用向量图
减少Native和Java代码
有许多方法能减少Native和Java代码的数量
减少不必要的生成代码
确保理解任何自动生成的代码。比如,许多protocol buffer工具生成了过多的方法和类,这会让你的应用大小翻倍。
移除枚举
一个枚举会让classes.dex增大1-1.4kb
减少Native库的大小
如果你的应用使用了Native代码和Android NDK,你也能通过优化代码减少应用体积,这里介绍的两个技巧是删除调试符号和避免抽取Native库。
移除调试符号
如果应用在开发中并且仍需要调试,那么我们能理解使用调试符号。使用Android NDK提供的arm-eabi-strip工具,能从Native库中删除不必要的调试符号,之后你再编译release包。
避免抽取Native库
在APK中存储未压缩的so文件,并且在Manifest文件的中设置android:extractNativeLibs为false,这会防止在安装时PackageManager将APK中的so文件拷贝到文件系统,避免这种拷贝会让应用在做增量更新时的更新包更小。
维持多个小的APK包
你的APK会包含用户下载了但从未使用的内容,比如地区或语言信息(译注:比如我是中国人,我就不会用到其他语种的资源)。为了给用户创建小的下载包,你能把你的应用拆分成多个APK,这些APK的差别在于一些因素(比如屏幕大小或者GPU纹理支持)。
当一个用户下载了应用,设备根据自身的特性和设置获取正确的APK。这种方式能够让设备不获取设备不需要的资源。比如,如果设备是hdpi的,那么他就不需要xxxhdpi的资源。
更多信息请看Configure APK Splits和Maintaining Multiple APKs。