若是你对App优化比较敏感,那么Apk安装包的大小就必定不会忽视。关于瘦身的缘由,大概有如下几个方面:javascript
下表为Apk目录及文件说明:java
文件/目录 | 说明 |
---|---|
assets/ | 存放一些静态文件,能够经过AssertManager访问 |
lib/ | 若是该目录存在,通常存放的是NDK编译出来的so |
META-INF/ | 保存着APK的签名信息 |
res/ | 资源文件所在目录,包含drawable、layout等 |
AndroidManifest.xml | 程序全局配置文件 |
classes.dex | Java Class,被DEX编译后可供Dalvik/ART虚拟机所理解的文件格式 |
resources.arsc | 编译后生成的二进制资源文件 |
作App瘦身以前须要对本身App现有组成有一个清晰的认识,上述解压的方式只能粗略的看出具体目录的大小,可是有用信息仍然有限。android
Android Studio 2.2以后有一个功能Analyze APK,方便简单,功能仍是Google自带的靠谱;git
ClassShark 是一款查看Android执行文件(apk)的浏览工具,能够很方便的打开APK/Class/Jar/res等文件和分析里面的内容。github
NimbleDroid 是美国哥伦比亚大学的博士创业团队研发出来的分析Android app性能指标的系统,分析的方式有静态和动态两种方式,其中静态分析能够分析出APK安装包中大文件排行榜,各类知名SDK的大小以及占代码总体的比例,各类类型文件的大小以及占排行,各类知名SDK的方法数以及占全部dex中方法数的比例。web
总结:这三种方式均可以对Apk的组成有一个更加清晰的认识,但更加推荐使用AndroidStudio自带的Analyze APK,简单、高效。算法
使用Analyze APK查看到文件大小以后发现,classes.dex、res、assets、lib等文件较大,哪里的脂肪多,咱们就去抽哪里。肯定优化方向:android-studio
随着版本的迭代,部分功能可能已被去掉,可是其代码还存在项目中。移除无用代码以及无用功能,有助于减小代码量,直接体现就是Dex的体积会变小。性能优化
备注:根据经验,不用的代码在项目中存在属于一个广泛现象,至关于僵尸代码,并且这类代码过多也会致使Dex文件过大。微信
备注:根据经验,项目中存在以前使用以后不使用的库的状况并不罕见。
代码混淆也称为花指令,是将计算机程序的代码转换为功能上等价可是难以阅读、理解的行为。Proguard是一个免费的Java类文件压缩、优化、混淆、预先验证的工具,能够检测和移除未使用的类、字段、方法、属性,优化字节码并移除未使用的指令,并将代码中的类、字段、方法的名字改成简短、无心义的名字。
能够看出Proguard不只能将diamante中的各类元素改的简短,还能够移除冗余代码,所以能够减小Dex文件的大小。
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android.txt'),
'proguard-rules.pro'
}
}
...
}复制代码
其中,proguard-android.txt是获取默认ProGuard设置,proguard-rules.pro文件用于添加自定义ProGuard规则。
备注:对于Proguard,虽然效果很明显,但仍然须要谨慎;
通常状况下缩减方法数,都是为了Android著名的64k方法数问题,此处再也不回顾,参见以前《关于Multidex的系列文章》。而这里说缩减方法数的目的,是为了App瘦身。
经过《Dalvik Executable format》,咱们能够看到Dex文件的组成。而从header-item表中的method-ids-size字段能够看出,方法数缩减以后,能够减小方法列表的大小;同时,方法在Dex文件中的占用空间也减小了,App天然被瘦身。
而缩减方法数,除了上面写到的广泛方法:移除无用方法、库、使用较小的SDK以外还有:
对于重要性,代码和资源的瘦身一样重要,可是从效果上来讲,资源文件的瘦身效果比代码的瘦身效果要好很是多。颇有可能费力许久在代码上获得的瘦身效果,在资源文件瘦身中轻松就获得了。
移除无用资源文件要比移除无用代码容易,在Android Studio的任何文件中右击,选择清除无用资源便可删除没有用到的资源文件。
备注:在build.gradle中设置shrinkResources为true后,每次打包的时候就会自动排除无用的资源。shrinkResources须要配合minifyEnabled一块儿使用。可是根据个人实验:无用的资源仍是会被打进Apk中,只是变成一张黑图,体积也很是小,只有不到100b。有使用错误的地方欢迎指正!
这条开发者中讨论的比较多,确实Google强烈建议根据不一样屏幕密度准备多套切图资源来作适配的。可是鉴于Android上对UI要求不会是最顶级的那种高度,以及即使是放在合适(注意这两个字)一个的目录下,在不一样的分辨率下也会作自动的适配(等比例拉伸、缩放);所以仍是建议:对UI不是最顶级要求的话根据本身的用户群体机型放在一个合适的目录下。这样毋庸置疑能够缩减Res的大小,进而减小Apk的体积。
备注:图片放在不恰当的目录有可能会对内存产生较大的影响,能够参考以前的文章《Android 性能优化(五)之细说 Bitmap》。
以前我在项目里发现过文件大小过1M的图片,多是因为UI同窗和RD同窗的双重疏忽,致使如此大的图片到了项目中,对Apk体积的影响天然不言而喻。
能够考虑使用TinyPng、pngquant、ImageOptim等工具对图片进行压缩,这些工具能够减小PNG文件大小,同时保持图像质量。
此处以TinyPng为例:TinyPng是一个至关不错的图片压缩工具,在保持alpha通道的状况下对PNG的压缩能够达到1/3以内,并且用肉眼基本上分辨不出压缩的损失。这张3.4M的图片被压缩到了984.7k,压缩率高达71%。
也有同窗开发了一个AndroidStudio插件:TinyPngPlugin,可以批量地压缩项目中的图片,更加方便。
备注:须要注意的是在Android构建流程中AAPT会使用内置的压缩算法来优化res/drawable/目录下的PNG图片,但也可能会致使原本已经优化过的图片体积变大,能够经过在build.gradle中设置cruncherEnabled来禁止AAPT采用默认方式优化咱们已经优化过的图片。
aaptOptions {
cruncherEnabled = false
}复制代码
PNG是一种无损格式,JPG是有损格式。JPG在处理颜色不少的图片时,根据压缩率的不一样,有时会去掉一些肉眼识别差距较小的中间颜色。可是PNG对于无损这个基本要求,会严格保留全部的色彩数。因此图片尺寸大,或者色彩数量多特别是渐变色的多的时候,PNG的体积会明显大于JPG。
在这种状况下,咱们能够有所取舍。小尺寸、色彩较少或者有alpha通道透明度的时候,使用PNG;大尺寸、色彩渐变多的使用JPG。
备注:根据经验,对于能够直接使用JPG格式的图片,最好不要从PNG转换为JPG,而是出图的时候直接出JPG格式的图片,相对而言,后者的效果更好。
可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。SVG由W3C制定,是一个开放标准。可使用矢量图形来建立独立于分辨率的图标和其余可伸缩图片。使用矢量图片可以有效的减小App中图片所占用的大小,矢量图形在Android中表示为VectorDrawable对象。
优势
缺点
Google于2010年提出了一种新的图片压缩格式 — WebP,为图片提供了无损和有损压缩能力,同时在有损条件下支持透明通道。据官方实验显示:无损WebP相比PNG减小26%大小;有损WebP在相同的SSIM(Structural Similarity Index,结构类似性)下相比JPEG减小25%~34%的大小;有损WebP也支持透明通道,大小一般约为对应PNG的1/3。同时,谷歌于2014年提出了动态WebP,拓展WebP使其支持动图能力。动态WebP相比GIF支持更丰富的色彩,而且也占用更小空间,更适应移动网络的动图播放。
优势:
缺点:
在Apk打包过程当中,aapt会将每个资源生成一个对应的int数值,而咱们经过这个int值来查找使用资源。在Apk构成中,咱们能够看到里面有一个resources.arsc文件,里面保存着资源id和资源key的映射关系。
当调用图片时,先找到drawable分类,再根据当前的系统config找到匹配的config表,根据id找到对应的res数据。drawable在arsc中是当作string类型保存的,res数据中有这个资源在res string pool池中的索引。根据这个索引能够在字符串池中找到一个字符串。这个字符串其实就是一个路径,好比:res/drawable-xhdpi/icon.png;混淆就是将这个路径改成R/s/f.png;同时修改resources.arsc文件的映射关系。这样就能清楚的看出来资源混淆能减少Apk的缘由:
这里推荐微信的资源混淆方案:AndResGuard。
将部分使用频率不高的资源例如图片,放在网上,在恰当的时机提早下载,这样也能节约部分空间。
So(shared object,共享库)是机器能够直接运行的二进制代码,是Android上的动态连接库,相似于Windows上的dll。每个Android应用所支持的ABI是由其APK提供的.so文件决定的,这些so文件被打包在apk文件的lib/目录下。
So的常见的场景如:加解密算法、音视频编解码、核心代码等。在生成SO文件时,须要考虑适配市面上不一样手机CPU架构,而生成支持不一样平台的SO文件进行兼容。目前Android共支持七种不一样类型的CPU架构,分别是:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),Mips (从2012年起),ARMv8,Mips64和x86_64 (从2014年起)。
理论上对应CPU架构的So的执行效率是最高的,可是这样会致使在libs目录下放置各个架构平台的So文件,Apk文件的大小天然也就更大了。那么咱们天然想到缩减Libs的目录,通常状况(注意限定)下留下armeabi目录便可,armeabi目录下的So能够兼容别的平台的So,可是性能会有所损耗,失去对特定平台的优化。
所以须要根据本身使用到的So功能来作具体的区分:对于性能敏感模块使用的So能够都放在armeabi目录,而后经过代码判断设备的CPU类型,再加载其对应架构的SO文件,例如微信就是这么作的。既缩减了Apk的体积,也不影响性能敏感模块的执行。
移除特定平台So的方式,这样打包就只保存armeabi里的So。
ndk {
//设置支持的SO库架构
abiFilters 'armeabi'
}复制代码
备注:本来x86架构的CPU是不支持运行arm架构的So,但Intel和Google合做在x86机子的系统内核层之上加入了一个名为houdini的Binary Translator(二进制转换中间层),这个中间层会在运行期间动态的读取arm指令并将之转换为x86指令去执行。
咱们知道Apk文件实际上就是一个Zip文件。Android SDK的打包工具apkbuilder采用的是Deflate算法将Android App的代码、资源等文件进行压缩,压缩成Zip格式,而后签名发布。
既然是压缩,那能不能改进其压缩方式,获取更小的Apk文件?经过分析Apk打包的流程图咱们能够发现SignedJarBuilder类对整个工程包括代码Dex和一些课压缩的资源、文件进行压缩,使用的是JDK中zip包下提供的算法。
简单的方式咱们能够在不改变App编译器工做的状况下,对生成的Apk文件进行二次压缩,一样使用Deflate算法,可是将压缩等级从标准提高到极限压缩。提升压缩级别可在不对Apk包自己的内容作任何修改的状况下获得更小的Apk。
备注:
使用7Zip对Apk进行极限压缩。
参考:
欢迎关注微信公众号:按期分享Java、Android干货!