Android的apk文件越来越大了这已经是一个不争的事实。

在Android 还是最初版本号的时候,一个app的apk文件大小也还仅仅有2 MB左右,到了如今。一个app的apk文件大小已经升级到10MB到20MB这个范围了。apk文件大小的爆炸式增长主要是由于用户对app质量的期待越来越高以及开发人员的开发经验增长,详细体如今下面几个方面:

  • dpi
  • Android平台的进化,开发工具的改进以及开源类库生态系统的丰富
  • 用户对高质量UI的期待
  • 其它原因

Android开发人员在设计一个app的时候应该将终于公布一个轻量级app作为一个最佳实践来考虑。为什么?首先这就意味着你拥有了一个简洁。易维护代码基础。

其次。官方应用商店对超过50MB的apk设置了拓展包文件下载选项。apk文件在50MB下面更easy让用户下载。最后,我们的应用程序环境是一个带宽有限,存储空间有限的环境,apk安装文件越小。下载就会越快。安装也会更快,良性循环,最后说不定用户由于这个给好评。

在大部分情况下,apk大小的增长是为了满足消费者的须要和期待。然而。我觉得apk大小的增速已经超过了用户对app期待的增速。所以,非常大程度上。官方应用商店里面的那些程序可以瘦身至它们如今大小的一半甚至很多其它。在这篇文章里面,我将写下一些关于怎样给apk文件瘦身的招式。希望你们可以喜欢。

 

APK 文件格式

unzip 命令解压这个apk文件一探apk的内部结构。以下的文件结构就是我们使用 unzip <your_apk_name>.apk1这个命令所获得的:


/assets
/lib
  /armeabi
  /armeabi-v7a
  /x86
  /mips
/META-INF
  MANIFEST.MF
  CERT.RSA
  CERT.SF
/res
AndroidManifest.xml
classes.dex
resources.arsc


我们可能对上面大部分的文件和文件夹都非常熟悉。它们和我们在实际开发app的时候所看到得项目结构一样,包括了: /assets/lib/resAndroidManifest.xml.

另一些文件可能是我们第一次看到。一般说来,classes.dex,

它包括了我们所写的Java代码经过编译后class文件。resources.arsc 包括了预编译之后的资源文件(比方values文件,XML

drawables 文件等。)。

因为apk文件仅仅是一个简单地压缩文件。这就意味着它有两种大小:即压缩前的大小和压缩后的大小。这篇文章我将主要关注压缩后的大小。

怎样降低apk文件大小

降低apk文件大小能够从几个方面入手。

因为每一个app都是不同的,所以没有什么绝对规则来给apk文件瘦身。作为apk文件的三个重要组成部分,我们能够考虑从它们開始入手:

  • Java 源码
  • 资源文件(resources/assets)
  • native code

所以接下来的招式都是由降低这些组件的大小出发,进而降低整个app的大小。

掌握良好的编码习惯

这是降低apk文件至关重要的第一步。你要对自己的代码了如子掌。你要移除掉全部无用处的dependency libraries,让你的代码一天比一天优秀,持续地优化你的代码。总而言之,保持一个简洁,最新的代码基础是降低apk文件至关重要的一环。

当然。从零開始一个项目并为这个项目保持一份简洁的代码基础非常easy。项目越老,这个工作就越困难。

其实,拥有一大段历史背景的项目必需要去处理各种死代码和无用代码。还好有很多的开发工具能够帮我们来做这些事情……

使用 Proguard

Proguard 是一个非常强悍的工具,它能够帮你在代码编译时对代码进行混淆。优化和压缩。它有一个专门用来降低apk文件大小的功能叫做 tree-shaking。Proguard 会遍历你的全部代码然后找出无用处的代码。

全部这些不可达(或者不须要)的代码都会在生成终于的apk文件之前被清除掉。Proguard 也会重命名你的类属性,类和接口。然整个代码尽可能地保持轻量级水平。

或许如今你会觉得 Proguard 是一个相当有效地工具。

可是能力越大,责任也就越大。

如今很多开发这觉得Proguard有点让人不省心,由于它会重度依赖反射。哪些类或者属性须要被处理或者不能处理都要开发人员对 Proguard 进行配置。

广泛使用 Lint

my_image 在 res/drawable 目录中,没有被使用,Proguard

仅仅会移除掉 R./gradlew lint这个简单地命令它就能帮你检查全部没用的资源文件。它在检測完之后会提供一份具体的资源文件清单。并将没用的资源列在“UnusedResources:

Unused resources” 区域之下。

仅仅要你不通过反射来反问这些无用资源,你就能够放心地移除这些文件了。

/res 目录以下的文件) ,可是会跳过 assets 文件 ( /assets

因此。Lint 也不能判定某个 asset 文件在项目中是否实用。

这全取决于开发人员对这个目录的维护了。

假设你没有使用某个asset 文件,那么你就能够直接清除这个文件。

对资源文件进行取舍

Android 支持多种设备。

Android的系统设计让它能够支持设备的多样性:屏幕密度。屏幕形状。屏幕大小等等。到了Android 4.4。它支持的屏幕密度包含: ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi and xxxhdpi。

可是你要知道的一点是,Android 支持这么多的屏幕密度并不意味着你须要为每个屏幕密度提供对应的资源文件。

2

我这么做得原因非常easy。首先。这些设备屏幕密度就能覆盖我 80% 的用户。其次,xxxhdpi 这个屏幕密度仅仅是在为未来的设备做准备。可是未来还未到来。最后。我真的不怎么关心低屏幕密度(比方mdpi 和 ldpi),不管我为这几个屏幕密度努力。结果都是令人伤心地,还不如直接让Android系统对 hdpi 资源文件进行适当地缩放来匹配对应地低端机型。

drawable-nodpi

资源文件最少化配置

Android 开发常常会依赖各种外部开源码库。比方Android Support Library, Google Play Services, Facebook SDK 等等。可是这些库里面并非全部的资源文件你都会用到。比方。 Google Play Services 里面会有一些为其它语种提供翻译。而你的app又不须要这个语种的翻译,并且这个库里面还包括了我的app中不支持的 mdpi 资源文件

resConfig 和 resConfigs

以下的 DSL (Domain Specific Language)就会阻止 aapt(Android Asset Packaging Tool)打包app中不须要的资源文件。


defaultConfig{
    // ...
    resConfigs"en","de","fr","it"
    resConfigs"nodpi","hdpi","xhdpi","xxhdpi","xxxhdpi"
}





压缩图片

Aapt(Android Asset Packaging Tool)就内置了 保真图像压缩算法。比如,一个仅仅需 256 色的真彩PNG图片会被aapt 通过一个颜色调色板转化成一个 8-bit PNG 文件。这能够帮助你降低图片文件的大小。

当然你还能够通过Google查找对应的优化工具,比方 pngquant, ImageAlpha 和 ImageOptim 等。

你能够从中选择一个适合你的工具。

另一种仅仅在Android平台上存在的图片文件也能够优化,它就是 9-patches。

就我眼下所知道。我还没发现有这个文件的优化工具。

然而你仅仅须要求你的设计师将它的可扩展区域和内容区域尽可能地降低就可以。

这不但能够降低资源文件的大小,还能使得以后资源文件的维护变得更加简单。

限制app支持的cpu 架构的数目

一般说来Android 使用Java 代码即能够满足大部分需求,只是还是有一小部分案例须要使用一些 native code。就像之前对资源文件那样opinionated,你能够这些 native code opinionated。 在当前的Android 生态系统中,让你的app支持 armabi 和 x86 架构就够了。

这里有一篇相当不错的关于怎样瘦身native 代码库的文章,你能够參考參考。

尽可能地重用

ListView 或者 RecyclerView,重用能够帮助你在列表滚动时保持界面流畅。重用还能够帮你降低apk文件的大小。比如,Android

提供了几个工具为一个asset文件又一次着色,在Android L中你能够使用 android:tint 和 android:tintMode 来达到效果,在老版本号中则能够使用 ColorFilter

假设系统中有两种图片,一种图片是还有一种图片翻转180°得到的,那么你就能够移除一种图片。通过代码实现。

比方你如今有两种图片分别命名为 ic_arrow_expand 和ic_arrow_collapse

 

ic_arrow_collapse 文件。然后在ic_arrow_expand 的基础上创建一个 RotateDrawable

这种方法也能够让你降低设计人员的工作:


<?xmlversion="1.0"encoding="utf-8"?>
<rotatexmlns: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"/>


 


在合适的时候使用代码渲染图像

在某些情况下。直接使用Java 代码渲染图像也能获得不错的效果。比方逐帧动画就是一个非常好的样例。近期我都在尝试一些Android Wear 的开发,了解了一下Android wearable support library。就像其它的Android support library 一样。这个库里面也有一些工具来处理穿戴设备的。

只是让我惊讶的是,当我简单地构建了一个 “Hello World”演示样例,最后得到的apk文件居然有1.5MB。

于是我高速地研究了一下 wearable-support.aar 文件。发现这个库有两个逐帧动画,并分别支持了3种不同的屏幕密度:一个

“success” 动画 (31 frames) 和一个 “open on phone” 动画 (54 frames)。

 

AnimationDrawable


<?xmlversion="1.0"encoding="utf-8"?>
<animation-listxmlns:android="http://schemas.android.com/apk/res/android"android:oneshot="true">
    <itemandroid:drawable="@drawable/generic_confirmation_00163"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00164"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00165"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00166"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00167"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00168"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00169"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00170"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00171"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00172"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00173"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00174"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00175"android:duration="333"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00185"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00186"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00187"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00188"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00189"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00190"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00191"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00192"android:duration="33"/>
    <itemandroid:drawable="@drawable/generic_confirmation_00193"android:duration="33"/>
</animation-list>




这样做得优点就是 (我当然在讽刺) 每帧显示33ms,这使得整个动画保持在30fps的频率。假设每帧16ms这将会导致整个库是之前的两倍大。

假设你去看源代码你会发现非常有趣。

在 generic_confirmation_00175generic_confirmation_00185 紧跟着它。这个优化节省了9个类似的帧

(包括了从176 帧到 184 帧) 。只是最后奇妙的是 wearable-support.aar 居然奇妙的包括了这个9个全然没用的帧。并且还以3中屏幕密度展示。3

在代码中来渲染这种动画明显会非常花时间。然而当你维持动画执行在60fps这种频率能够大幅度的降低apk的大小。

在写这篇博文的时候,Android还没提供工具来渲染这种动画。可是我希望Google正在开发新一代的轻量级实时渲染系统来保证material design的细节呈现。

当然“Adobe After Effect to VectorDrawable” 之类的设计工具也能提供非常多方便。

怎样更进一步?

上面全部的招式都集中在app或者library 开发人员。或许我们还能够在app分发渠道方面为apk大小做出一些改变?我想能够在app 分发server端做一些改进,或者在官方应用商店。比如,我们能够期待官方应用商店在用户安装app的时候为设备绑定对应的native 库而摒弃那些没用的。

相同地,我们还能够想象仅仅依据目标设备的配置来打包应用。

不幸的是。这可能破坏Android 生态一个重要的功能特性:配置热置换。其实,Android一開始就是位处理各种实时的配置更改(语言,屏幕转向)而设计的。如果我们移除掉与目标屏幕不兼容的资源文件,这能够极大的降低文件大小。

只是Android须要处理实时的屏幕密度更改。即便我们如果废除这样的功能,我们仍然须要处理为不同的屏幕密度设计的图片以及其它配置(比方屏幕朝向。最小宽度等)。

server端的apk打包看起来非常强大。但这样会冒非常大得风险,由于终于传送给用户的apk会于开发人员发给的server的全然不同。

分发一些缺失资源文件的apk可能会导致app崩溃。

总结

设计就是在一个约束集里面找出最好的方案。显然apk文件的大小就是一个约束。不要害怕为了让多个方面变得更好而放松一个方面的约束。

比如,当你要减少UI的渲染效果时。不要犹豫。由于这能够让apk的大小减小,同一时候使得app的执行也更加流畅。你99%的用户是感受不到UI质量变低的。可是他们会注意到apk文件变小了,执行也更加流畅了。总之,你须要将app各方面进行总体考虑。而不是只几个方面的斟酌。