「Android」 APK瘦身探索
APK瘦身探索
最近几周一直在研究如何为APK瘦身,折腾了很久,是时候写篇博客总结一下了,虽然已经准备了下周一要在客户端周会分享用的PPT:APK瘦身探索。
价值
虽然说APK瘦身对于Android对应用可分配内存的限制影响不大,但是还是有一些影响的,就以图片为例,将一些小图标替换为iconfont能有效减小内存的分配,防止OOM的出现。
另外,无论是iOS开发者还是Android开发者都应该尝试最好学会如何为IPA或APK瘦身,不仅仅是为了帮助用户省流量、减少下载时间、减少占用的存储空间等等,更重要的是为了<font color='#ee6252'>提高转化率(注意:本文的转化率均指下载转化率)</font>。
那转化率是什么呢?
举个栗子:你的应用大小是 18MB ,有100个潜在用户想要去下载尝试使用,结果有20个用户嫌弃安装包太大直接扬长而去,有20个用户在等待下载的过程中取消下载,最终只有60个用户真正下载安装,那么应用的转化率就是 60/100 = 60% 。
那为什么要提高转化率呢?
因为用户在纠结下载你的产品还是你的竞品的时候,往往会选择那个体验最好、功能最多、性能最好、包最小的。
工具
如何有条理的为APK瘦身,必须知道APK的目录结构,不过在讲述这个之前,我这里先介绍几个工具,在后文会用到。
AndroidStudio2.2.3
AndroidStudio升级到版本2.2.3之后提供了Analyze APK的功能(不过作为AS粉想必已经升到AS2.3了吧,哈哈),我们可以借助该工具清楚的了解APK的下载大小、解压之后的大小、内部各个文件夹或文件占用的大小等信息,进而得知那些地方可以优化。下图为目前达人店APK的内部信息:
APK Info
另外使用该工具还可以反编译资源文件、还原layout中的资源id,分析DEX、显示每一个文件夹或文件的方法数,分析哪些第三方库方法数很多但实际只用到了一部分等其他功能。
最后如何使用该工具?有以下两种方式:
- 直接将APK拖拽进入AndroidStudio即可
- 点击菜单栏Build-Analyze APK...选项,再选择需要分析的APK即可
NimbleDroid
NimbleDroid 是美国哥伦比亚大学的博士创业团队研发出来的自动化分析Android app性能指标的系统,分析的方式有静态和动态两种方式:
其中静态分析可以分析出APK安装包中大文件排行榜,各种知名SDK的大小以及占代码整体的比例,各种类型文件的大小以及占排行,各种知名SDK的方法数以及占所有dex中方法数的比例,针对缓慢的方法,缓慢的第三方SDK和内存泄漏。
其中动态分析可以测量生成的速度、网络、内存和磁盘使用率。
我用了一下上面这个工具,感觉还是不错的,下面我们来看几张有逼格的图(图中数据来自于达人店APK):
APK内部各类型对应文件占用的大小
Size by Type
APK内部各个类库的方法数
Method Count
APK启动过程中各个方法的执行时间
Hung CPU Methods
ClassShark
ClassShark 是一款查看Android执行文件(apk)的浏览工具,目前有两个android App(Apk)和桌面(jar)的版本。
使用这款工具,可以很方便的打开APK、Class、Jar、res等文件和分析里面的内容。
ClassShark
不过目前这个工具并不需要我们单独使用,因为AS内部的分析工具就是用的这个。
通过以上工具,我们可以方便快速的分析APK,找出那些可以优化的部分,为了更加有条理的进行讲解,下面我会按照APK的目录结构来逐条阐述。
APK目录结构
APK_taobao
APK_weixin
可以看出淘宝和微信也是这样处理,所以我们其实也可以这样操作。
res目录
res目录用于存放应用程序的资源文件,主要包括布局文件、图片、XML配置文件等,它包含以下几个特性:
- 使用Resources类管理资源
- res目录内部文件会被系统编译
- res目录不支持任意深度的子目录
- 获取资源不需要通过相对路径找寻,因为文件会被系统编译,所以需要通过资源ID(注:资源ID被存放在resources.arsc文件中)查找
其基本目录结构如下图所示:
res目录结构
上图比较全面的列举了res目录下经常包含的子目录和文件,并解释了各个子目录或文件的含义。
根据上图我们显然可以发现,res目录就是我们APK瘦身里面的一大重要部分了,由于其包含的知识很大,下面我会分章节进行阐述,尽量一一阐述清楚。
只用一套图
首先我给出这么一个结论:一个APK尽量只用一套图片,从内存占用和适配的角度考虑,建议放在xhdpi文件夹下。
那么为什么要把图片放在xhdpi文件夹下面呢?
由于此处知识涉及到Android屏幕适配方面的知识,比较复杂。本文是关于APK瘦身的,所以就不详细讲解了。下面进行必要的阐述,首先让我们来看两张图:
Remove Unused Resources
然后根据查找到结果,逐条双击打开文件,按快捷键fn+option+F7进行查找,然后在分析是否应该删除,<font color='#ee6252'>注意:此处定要慎重,务必自测!!!</font>。
另外,我们还可以通过将某些图片转换网络图片的方式解决,但是这个操作也是需要<font color='#ee6252'>慎重</font>的,最重要的一点是<font color='#ee6252'>不能影响用户的体验</font>。这边路飞同学制作了一个Chrome插件来帮助我们快速上传图片到cdn中,可以通过此处下载:joyuploader。
最后,还有一种办法:使用AndroidStudio提供的Lint工具对工程做静态代码检查,它不仅可以找出我们在代码编写上面的失误,还能够列出那些未被使用的资源,甚至还可以指出哪些地方可能存在内存泄漏等等,功能非常庞大,所以如果工程较大的话,还是比较耗时的,当然这并不是问题,因为我们可以针对某个模块执行静态代码检查。我们可以通过<font color='#ee6252'>选中菜单栏Analyze-Inspect Code...选项</font>执行静态代码检查,执行完成的效果图如下所示:
Lint
资源混淆
目前我们不仅在资源(特指图片)质量方面做到了极致,在资源数量方面也做到了极致,看起来真的到极致了。其实不然,我们还可以使用资源混淆的方式为APK瘦身,通过压缩文件内容,减少文件名长度的方式实现。
目前比较出名的资源混淆方式是微信的AndResGuard和美团的修改AAPT,不过由于美团的资源混淆方法非常麻烦,还有如果需要通过getIdentifier的方式获取资源时需要保证这些资源的名字不被混淆,美团很难实现,所以目前大家都在用微信的资源混淆方式,因为微信使用方便,而且提供了白名单。
下面是达人店APK未使用微信资源混淆和使用了微信资源混淆的差异:
shopandroid-debug.apk
shopandroid-debug_signed_7zip_aligned.apk
可以清楚的看到,在使用了微信资源混淆之后,APK减少了0.7MB左右,效果还是十分明显的。
那么为什么使用了微信资源混淆之后可以实现APK瘦身呢?下面这两张图清楚的展示了瘦身的原因(摘自微信资源混淆官方文档):
总结,安装包大小减少的原因以下四个:
jar2dex
既然dx工具已经办帮我们压缩了那么多,那我们还有什么好压缩的呢?
当然有,因为dx工具并没有压缩class的内容,所以我们的源代码并没有得到压缩,下面我先介绍两种常见的办法来解决这个问题:
-
和之前最简单的资源压缩方式一样,我们也可以在主模块的gradle文件中配置
minifyEnabled实现代码混淆,从而减小java文件的大小。因为混淆后的代码将较长的文件名、实例、变量、方法名等等做了简化,从而实现字节长度上的优化。 -
我们也可以使用之前所说的AndroidStudio提供的Lint工具执行静态代码检查,进而删除无用的类、方法、变量等。
上面的两种方式虽然能够帮助我们减少APK的大小,但是实际上我们还可以做的更多。因为上面两种方式是自动化的,所以必然不像人那么智能。
有时候我们可能遇到这样的情况:我们引用了一个第三方类库,但是只用到了其中的几个功能,其他的大部分功能一直不用,这不是白白的浪费了用户的流量,降低了APK的下载转化率么?为了解决这样的问题,我们必须借助一些工具去找到这样的类库,比如在文章开头介绍过的工具:NimbleDroid,下面是达人店APK目前存在的某个这样的第三方类库:
Method Count Of ZXing
该类库在达人店APK中只用到了扫一扫和生成二维码这两个功能,然而这个类库定义的方法数竟然有1428,显然不合理,完全可以抽离其内部的扫一扫和生成二维码代码,单独实现。
ReDex
最后介绍一下Facebook开源的一个减少APK大小以提高性能的工具 -- ReDex,它通过内嵌以及清除僵尸代码这样的优化方式来减少字节码,其主要是对DEX做了优化,能够让APK运行更快,不过需要多测试是否会崩溃。
下面分别是ReDex的教程地址和GitHub地址:
facebook/redex: A bytecode optimizer for Android apps
我个人由于时间问题,目前还未接入该工具,下面就展示一下网友u012124438的测试数据好了:
ReDex
后来我在使用 Redex 压缩和优化 Android APK一文的帮助下,成功的安装并使用了ReDex,但是发现了以下两个问题:
- 用ReDex处理过的APK,其内部的META-INF目录会被删除,虽然可以将ReDex处理过的APK用AndResGuard处理一下,会重新出现该目录。
- 用ReDex处理过的APK,安装好之后会出现Crash的问题,目前达人店APK要Crash三次之后才能正常使用,而且它对于达人店APK的贡献只有20~25K,所以并未打算接入。
resources.arsc
最后就是对于resources.arsc文件的压缩处理了,其实这里我们也不需要做什么,首先因为该文件记录的是资源文件和资源ID的映射关系,并没有什么值得压缩的地方;另外如果真的有值得压缩的地方,也只有资源文件的名字了,不过这个早就在之前因为我们使用了微信资源混淆解决了,所以此处就不再多讲了。
至此,针对于APK目录结构,逐条分析如何为APK瘦身已经讲完了,接下来介绍一下其他的压缩方式。
其他方式
使用APK Splits构建APK
虽然我们上面很好的使用了resource shrinker可以回收一些未使用的资源(v7、v4、google Service 等Libarry资源),但有些资源仍然未被清除。
例如:那些未使用的多套替代资源,或者是library内部隐患着引用着的资源而我们却没有使用到。或者是我们要根据用户的手机去提供不同版本的APK,如分辨率(xxhdpi,mhdpi等),so库等。那么我们可以使用APK Splits大大的减少一些无用的资源,这里我们主要参考了http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits文档。
使用多版本的APK
Multiple APK Support是一个在Google Play,可以发布不同的应用程序,分别针对不同的设备配置特征。每个APK是一个完整的、独立的应用程序版本,但他们分享在Google Play相同的应用程序清单,必须共享相同的包名和与签名。Google Play 会自动给你匹配相应的APK,这样我们的APK 就可以是分不同版本构建需要资源文件,从而减小APK的大小。
资源动态加载
我们可以在项目中使用资源动态加载形式,例如:表情,语言,离线库等资源动态加载,减小APK的大小。
支持插件化
未来对于一些独立业务模块,可以做成插件化动态加载,用户需要使用时,只需下载少部分插件。
使用shape背景
特别是在扁平化盛行的当下,很多纯色的渐变的圆角的图片都可以用shape实现,代码灵活可控,省去了大量的背景图片。
使用着色方案
相信你的工程里也有很多selector文件,也有很多相似的图片只是颜色不同,通过着色方案我们能大大减轻这样的工作量,减少这样的文件。
借助于android support库可实现一个全版本兼容的着色方案,参考代码:DrawableLess.java
其他方式...
总结
如果你不是一个Android开发人员,对于之前的阐述可能非常模糊,并不能明显的表现出APK瘦身这件事情的意义,下面我就来总结一下:
首先,针对于达人店APK做了哪些应用?
-
字体文件使用Glyphs进行压缩,图片使用tinypng进行压缩
-
只是用一套so文件
-
只使用一套图
-
使用WebP图片或SVG图片替换某些PNG或JPG图片
-
使用Google提供的Gradle插件实现代码混淆和资源混淆
-
借助NimbleDroid、AS Anylze/Lint工具分析查找删除不需要的代码或资源
-
使用微信提供的资源混淆工具(处于稳定性测试阶段)
-
使用Facebook提供的ReDex工具(处于调研阶段)
其次,APK瘦身的效果如何?
这里我们就直接看图好了:
APK瘦身效果
可以明显的发现,达人店APK越来越小了,所以说,这个工作是很有意义的。
最后,做一下竞品分析?
竞品分析
根据上面图表,可以发现我们的APK的大小是十分有优势的,能够很好的提高下载转化率。而iOS那边显然就比较大了,虽然两者之间不能比较,但是还是可以进行一系列优化的。
忠告
最后的最后,我想对大家说:在APK瘦身的道路上,一定要掌握好度,安排好事情的优先级,如果目前要做的事情、要优化的方面比较复杂,不仅需要花费很长的时间,而且最终效果也不明显,可以考虑之后再做,甚至不做。