Android 的 apk 文件越来越大了这已经是一个不争的事实.在 Android 还是最初版本的时候,一个 app 的 apk 文件大小也还只有 2 MB 左右,到了现在,一个 app 的 apk 文件大小已经升级到 10MB 到 20MB 这个范围了.apk 文件大小的爆炸式增长主要是因为用户对 app 质量的期待越来越高以及开发者的开发经验增长,具体体现在以下几个方面:
Android 设备 dpi 的多样化 ([l|m|tv|h|x|xx|xxx]dpi)
Android 平台的进化,开发工具的改进以及开源类库生态系统的丰富
用户对高质量 UI 的期待
其他原因
Android 开发者在设计一个 app 的时候应该将最终发布一个轻量级 app 作为一个最佳实践来考虑.为什么?首先这就意味着你拥有了一个简洁,易维护代码基础.其次,官方应用商店对超过 50MB 的 apk 设置了拓展包文件下载选项,apk 文件在 50MB 以下更容易让用户下载.最后,我们的应用程序环境是一个带宽有限,存储空间有限的环境,apk 安装文件越小,下载就会越快,安装也会更快,良性循环,最后说不定用户因为这个给好评. 在大部分情况下,apk 大小的增长是为了满足消费者的需要和期待.然而,我认为 apk 大小的增速已经超过了用户对 app 期待的增速.所以,很大程度上,官方应用商店里面的那些程序可以瘦身至它们现在大小的一半甚至更多.
APK 文件格式 在说如何给 apk 瘦身之前,让我们先来看看 apk 文件内部的结构到底是怎么一回事.说简单点,一个 apk 文件就是包含一些文件的压缩包.作为开发者,我们通过使用 unzip 命令解压这个 apk 文件一探 apk 的内部结构.下面的文件结构就是我们使用 unzip .apk
这个命令所获得的:
/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 /res AndroidManifest.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 文件至关重要的一环. 当然,从零开始一个项目并为这个项目保持一份简洁的代码基础很容易.项目越老,这个工作就越困难.事实上,拥有一大段历史背景的项目必须要去处理各种死代码和无用代码.还好有许多的开发工具可以帮我们来做这些事情......
使用 Proguard
Proguard 是一个很强悍的工具,它可以帮你在代码编译时对代码进行混淆,优化和压缩.它有一个专门用来减少 apk 文件大小的功能叫做 tree-shaking.Proguard 会遍历你的所有代码然后找出无用处的代码.所有这些不可达(或者不需要)的代码都会在生成最终的 apk 文件之前被清除掉.Proguard 也会重命名你的类属性,类和接口,然整个代码尽可能地保持轻量级水平. 也许现在你会认为 Proguard 是一个相当有效地工具.但是能力越大,责任也就越大.现在许多开发这认为 Proguard 有点让人不省心,因为它会重度依赖反射.哪些类或者属性需要被处理或者不能处理都要开发者对 Proguard 进行配置.
广泛使用 Lint
Proguard 只会对 Java 代码起作用,那么对哪些资源文件呢?比如一张图片 my_image 在 res/drawable 文件夹中,没有被使用,Proguard 只会移除掉 R 类中的引用,但是图片依然还在文件夹中. Lint 一个静态的代码分析器,你只需通过调用./gradlew lint 这个简单地命令它就能帮你检查所有无用的资源文件.它在检测完之后会提供一份详细的资源文件清单,并将无用的资源列在 "UnusedResources: Unused resources" 区域之下.只要你不通过反射来反问这些无用资源,你就可以放心地移除这些文件了. Lint 会分析资源文件 (比如 / res 文件夹下面的文件) ,但是会跳过 assets 文件 (/assets 文件夹下面的文件).事实上 assets 文件是可以通过它们的文件名直接访问的,而不需要通过 Java 引用或者 XML 引用.因此,Lint 也不能判定某个 asset 文件在项目中是否有用.这全取决于开发者对这个文件夹的维护了.如果你没有使用某个 asset 文件,那么你就可以直接清除这个文件.
对资源文件进行取舍
Android 支持多种设备.Android 的系统设计让它可以支持设备的多样性:屏幕密度,屏幕形状,屏幕大小等等.到了 Android 4.4,它支持的屏幕密度包括: ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi and xxxhdpi.但是你要知道的一点是,Android 支持这么多的屏幕密度并不意味着你需要为每一个屏幕密度提供相应的资源文件. 如果你知道某些屏幕密度的设备只有很少部分用户在使用,那么你就可以直接不需要使用相应屏幕密度的资源文件.就我个人而言,我只会为我的应用提供 hdpi, xhdpi and xxhdpi 这几个屏幕密度的支持.如果某些设备不是这几个屏幕密度的,不用担心,Android 系统会自动使用存在的资源为设备计算然后提供资源文件. 我这么做得原因很简单.首先,这些设备屏幕密度就能覆盖我 80% 的用户.其次,xxxhdpi 这个屏幕密度只是在为未来的设备做准备,但是未来还未到来.最后,我真的不怎么关心低屏幕密度(比如 mdpi 和 ldpi),无论我为这几个屏幕密度努力,结果都是令人伤心地,还不如直接让 Android 系统对 hdpi 资源文件进行适当地缩放来匹配相应地低端机型. 同样地,在 drawable-nodpi 文件夹里面维持一个文件也能节省空间.当然前提是你觉得对这个文件进行相应地缩放之后呈现的效果你能接受或者这个文件出现的几率很少.
资源文件最少化配置
Android 开发经常会依赖各种外部开源代码库,比如 Android Support Library, Google Play Services, Facebook SDK 等等.但是这些库里面并不是所有的资源文件你都会用到.比如,Google Play Services 里面会有一些为其他语种提供翻译,而你的 app 又不需要这个语种的翻译,而且这个库里面还包含了我的 app 中不支持的 mdpi 资源文件,还好从 Android Gradle Plugin 0.7 开始,你可以配置你的 app 的 build 系统.这主要是通过配置 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.这个方法也可以让你减少设计人员的工作:
在合适的时候使用代码渲染图像
<?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" />
在某些情况下,直接使用 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).
这样做得好处就是 (我当然在讽刺) 每帧显示 33ms,这使得整个动画保持在 30fps 的频率.如果每帧 16ms 这将会导致整个库是之前的两倍大.如果你去看源码你会发现很有趣, 在 generic_confirmation_00175 这一帧 (15 行) 将持续显 333ms.generic_confirmation_00185 紧跟着它.这个优化节省了 9 个类似的帧 (包含了从 176 帧到 184 帧) .不过最后神奇的是 wearable-support.aar 竟然神奇的包含了这个 9 个完全无用的帧,而且还以 3 中屏幕密度展示.在代码中来渲染这样的动画明显会很花时间.然而当你维持动画运行在 60fps 这样的频率可以大幅度的减少 apk 的大小.在写这篇博文的时候,Android 还没提供工具来渲染这样的动画.但是我希望 Google 正在开发新一代的轻量级实时渲染系统来保证 material design 的细节呈现.当然 "Adobe After Effect to VectorDrawable" 之类的设计工具也能提供很多方便.
如何更进一步?
上面所有的招式都集中在 app 或者 library 开发者.也许我们还可以在 app 分发渠道方面为 apk 大小做出一些改变?我想可以在 app 分发服务器端做一些改进,或者在官方应用商店.例如,我们可以期待官方应用商店在用户安装 app 的时候为设备绑定相应的 native 库而摒弃那些无用的. 同样地,我们还可以想象只根据目标设备的配置来打包应用.不幸的是,这可能破坏 Android 生态一个重要的功能特性:配置热置换.事实上,Android 一开始就是位处理各种实时的配置更改(语言,屏幕转向)而设计的.如果我们移除掉与目标屏幕不兼容的资源文件,这可以极大的减少文件大小.不过 Android 需要处理实时的 屏幕密度更改 .即便我们假设废除这种功能,我们仍然需要处理为不同的屏幕密度设计的图片以及其他配置(比如屏幕朝向,最小宽度等). 服务器端的 apk 打包看起来很强大.但这样会冒很大得风险,因为最终传送给用户的 apk 会于开发者发给的服务器的完全不同.分发一些缺失资源文件的 apk 可能会导致 app 崩溃.
总结
设计就是在一个约束集里面找出最好的方案.显然 apk 文件的大小就是一个约束.不要害怕为了让多个方面变得更好而放松一个方面的约束.例如,当你要降低 UI 的渲染效果时,不要犹豫,因为这可以让 apk 的大小减小,同时使得 app 的运行也更加流畅.你 99% 的用户是感受不到 UI 质量变低的,但是他们会注意到 apk 文件变小了,运行也更加流畅了.总之,你需要将 app 各方面进行整体考虑,而不是仅仅几个方面的斟酌.
来源: https://juejin.im/post/5a5c84d4518825734859efc1