竹山一叶

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

前言

用户通常会避免下载比较大的应用,特别是连接到2G和3G网络,或者按流量收费的设备。这篇文章描述了如何减小apk的大小,帮助你让更多的用户下载你的app。

一 理解APK的结构

在讨论如何减小apk大小之前,理解apk的结构很有必要。一个APK文件包括一个ZIP 文件,该ZIP包含app的所有文件。包括java 字节码文件,资源文件和一个包含了编译后的资源文件。APK包含以下目录:

  • META-INF/:包含了CERT.SF 和 CERT.RSA 签名文件, 以及 MANIFEST.MFmanifest 文件.
  • assets/: 包含了app的assets,app可以通过 AssetManager 对象获取到这些资源
  • res/: 包含了那些没有被编译到 resources.arsc的资源
  • lib/: 包含了用于软件处理器的编译代码,该目录包含一个子目录,针对不同平台: armeabiarmeabi-v7aarm64-v8ax86x86_64, and mips.

一个APK也包含了下面的文件,但只有 AndroidManifest.xml 是强制性的

  • resources.arsc:

    包含了编译后的资源。该文件包含了 res/values/文件夹下的所有XML内容。打包工具抽取了XML内容,将它编译成二进制格式,并且进行了压缩。该内容包括language strings和styles,以及未直接包含在resources.arsc 文件中的内容路径。比如layout文件和图片。

  • classes.dex:

    包含可以被Dalvik/ART 识别,以dex文件格式编译后的代码

  • AndroidManifest.xml:

    包含了Android核心mainfest文件。该文件罗列了app名字,版本,访问权限,和引用的library文件。该文件采用二进制XML格式。

二 减少资源的数量和大小

APK的大小对app的加载速度以及内存的使用和电量消耗都有影响。一种减小APK大小的最简单方法就是减少APK的资源文件数量和大小。也可以移除那些app不再使用的资源,或者使用可扩展的 Drawable 对象替代图片文件。这部分讨论了这些方法,以及其它几种减小app资源以便最终达到减小APK总体大小的其它方法。

移除无用资源

使用 lint 工具,AndroidStudio中的一个静态的代码分析工具。可以检测res/ 目录下那些没有被引用的资源. 当 lint工具发现了项目中潜在的无用资源,就会打印类似如下的信息:

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

注意: lint 工具不能够扫描assets/ 目录, assets 资源是通过反射的方式引用的,或者app中引用的其它library 文件。但lint并不会移除资源,它只会提示它们的存在。

你引入的Libraries有可能引入了无用的资源。Gradle可以通过在 build.gradle 文件中开启shrinkResources 来帮你自动的移除这些资源:

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通过 resConfig 和 resConfigs flavors 和 defaultConfig 选项把这些信息传递给构建系统。构建系统会阻止来自其它不受支持的资源出现在APK中,从而减少APK的大小。更多信息可以参考 Remove unused alternative resources

最小化使用Libraries中的资源

在开发App的时候,通常会使用外部libraries去提升app的可用性和功能扩展。比如,你可能会引用Android Support Library去提升老设备的用户体验。或者使用 Google Play Services 为app提供自动翻译。

如果一个library被设计用于桌面服务,那么就可能包含很多app不需要的对象和方法。为了只保留library中app需要的代码,如果license许可的话,你需要编辑library文件。你也可以使用一个移动端友好的替代库。

注意: ProGuard 可以清除一些从library中导入的不需要的代码。但是不会移除一个 library的内部依赖。

只支持特定的分辨率

Android支持非常大的设备集,包括各种屏幕密度。 在Android 4.4(API级别19)及更高版本中,框架支持各种分辨率:ldpi,mdpi,tvdpi,hdpi,xhdpi,xxhdpi和xxxhdpi。 虽然Android支持所有这些分辨率,但你不需要导出光栅化资源到每种分辨率。

如果你知道只有一小部分用户使用特定分辨率的设备,请考虑是否需要支持这些分辨率。 如果你不包括特定屏幕密度的资源,Android会自动缩放最初为其他屏幕密度设计的现有资源。

如果您的应用只需要缩放的图片,您可以通过在drawable-nodpi /中使用图片的单个版本来节省更多空间。 我们建议每个应用至少包含一个xxhdpi图片版本。

更多屏幕分辨率的信息,可以参考 Screen Sizes and Densities.

减少动画帧数

逐帧动画可能会大幅增加APK的大小。 图1中展示了一个帧动画被分成多个PNG文件的情况。 每个图像是动画中的一帧。

对于添加到动画中的每一帧,都会增加APK中存储的图片数量。 在图1中,图像在应用程序中以30 FPS动画。 如果图像仅以15FPS动画化,则动画将仅需要所需帧的数目的一半。

Use Drawable Objects

一些图像不需要静态图像资源; 框架可以在运行时动态地绘制图像。 相反,Drawable对象(XML中的)可能只会占用APK中的一小部分空间。 此外,XML形式的Drawable对象可以生成符合MaterialDesign指南的单色图像。

减少资源

你可能为同一种图像的不同形式都提供了独立的资源,例如同一图像的有色,阴影或旋转版本。 但是,我们建议你重复使用相同的资源,在运行时根据需要进行自定义。

Android提供了几个工具来更改资源的颜色,可以在Android 5.0(API级别21)以及更高版本上使用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" />

从代码中渲染

You can also reduce your APK size by procedurally rendering your images. Procedural rendering frees up space because you no longer store an image file in your APK.

你也可以通过程序对图像渲染来减少APK大小。 通过程序渲染可以节约空间是因为不需要在APK中存储图像文件。

压缩PNG文件

aapt工具可以在构建过程期间优化放置在res / drawable /中的图像资源,以及无损压缩。 例如,aapt工具可以将不需要多于256种颜色的真彩色PNG转换为具有调色板的8位PNG。 这样做会产生质量相同但占用内存较小的映像。

但请记得aapt有以下限制:

  • aapt工具不会收缩asset/文件夹中包含的PNG文件。
  • 图像文件需要使用256个或更少的颜色的aapt工具来优化它们。
  • aapt工具可能会填充已压缩的PNG文件。 为了防止这种情况,您可以在Gradle中使用cruncherEnabled标志为PNG文件禁用此过程:
aaptOptions {
    cruncherEnabled = false
}

压缩PNG和JPG文件

你可以使用像 pngcrushpngquant, 或zopflipng等工具来减少PNG文件大小,而不会损失图像质量。 所有这些工具都可以减少PNG文件大小,同时保持图像质量。

pngcrush工具特别有效:此工具在PNG过滤器和zlib(Deflate)参数上迭代,使用每个过滤器和参数的组合来压缩图像。 然后选择产生最小压缩输出的配置。

对于JPEG文件,您可以使用 packJPG 等工具将JPEG文件压缩为更紧凑的形式。

使用WebP 文件格式

除了使用PNG或JPEG文件,你还可以使用WebP的图像文件。 WebP格式提供有损压缩(如JPEG)和透明度(如PNG),但可以提供比JPEG或PNG更好的压缩。

但是,使用WebP文件格式有一些显着的缺点。 首先,在低于Android 3.2(API级别13)的平台版本中不支持WebP。 第二,系统解码WebP比PNG文件需要更长的时间。

注意:Google Play只接受使用PNG格式的图标。 如果你打算通过Google Play发布应用,图标就不能使用其他文件格式(如JPEG或WebP)。

使用矢量图形

你可以使用矢量图形创建分辨率独立的图标和其他可伸缩媒体。 使用这些图形可以大大减少APK体积。 矢量图像在Android中表示为VectorDrawable对象。 使用VectorDrawable对象,100字节的文件可以生成屏幕大小的清晰图像。

然而,系统渲染每个VectorDrawable对象需要大量的时间,较大的图像则需要更长的时间才能出现在屏幕上。 因此,只有在显示小图像时才考虑使用这些矢量图形。

有关使用VectorDrawable对象的更多信息,请参考 Working with Drawables.

三 减少Native和Java代码

删除不必要的生成代码

确保你能够理解那些任何自动生成的代码部分。 例如,许多协议缓冲工具生成过多的方法和类,可以使应用程序的大小增加一倍或两倍。

删除枚举

单个枚举可能为应用程序的classes.dex文件添加大小为1.0到1.4 KB的大小。 对于复杂的系统或者共享库,这种增加可能比较快迅速。 如果可能,请考虑使用@IntDef注解和ProGuard 来除去枚举并将它们转换为整数。 这种类型转换保留了枚举的所有类型安全的好处。

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

如果你的应用使用本地代码和Android NDK,你还可以通过优化这些代码来减小应用的大小。 两个有用的方式是删除debug标记,不提取本地库。

删除Debug符号

如果你的应用程序正在开发中并仍需要调试,那么使用debug标记很有意义。 使用Android NDK中提供的 arm-eabi-strip 工具从本地库中删除不必要的调试标记。 之后,再编译release版本。

避免提取本地库

将.so文件存储在APK中未压缩的文件中,并在应用清单的``元素中将android:extractNativeLibs标记设置为false。 这将防止PackageManager在安装过程中将.so文件从APK复制到文件系统,并且有一个额外的好处,会让app的差分更新变得更小。

四 维护多个精简版APK

你的APK可能包含用户下载但从不使用的内容,例如区域或语言信息。 为了让用户提供最小化的下载,你可以将应用细分为多个APK,并根据屏幕尺寸或GPU纹理支持等因素进行区分。

当用户下载您的应用时,其设备会根据设备的功能和设置接收正确的APK。 这样,设备不会接收设备没有的功能的资源。 例如,如果用户拥有的是hdpi设备,那么他们不需要你为更高分辨率设备提供的xxxhdpi资源。

更多信息请参考Configure APK Splits and Maintaining Multiple APKs.

 

来源:http://www.lightskystreet.com/2016/10/17/android-optimize-apk/

posted on 2017-05-17 15:09  竹山一叶  阅读(2349)  评论(0编辑  收藏  举报