瘦身目的
开发过程中,随着功能不断的迭代,包体积也会逐渐变大,如果此时将包投入市场,将会引来客户的投诉和抱怨。体积大一方面是浪费用户数据流量,另一方面是增加了安装的等待时间,用户可能因为嫌弃安装包太大扬长而去,对企业利润有着直接的冲击,所以应用瘦身环节尤为重要。下面以企业项目做实例分享一下瘦身经验。
基本思路
其实很简单,主要分两大步骤:了解安装包的组成部分,对安装包的资源目录瘦身
在代码结构双击打包好的apk会出现以下信息,可以直观的看到安装包结构:
通过上图,我们可以知道哪些模块最影响包的体积,根据对体积的影响程度做好排序,优化方向需围绕以下几个目录。
- assets
- lib
- res
- .dex
- .arsc
-
assets。该目录下存放资源文件,例如音频文件、json、web等。但下图可以看到该目录下存放的全是字体
针对字体,给出两种建议:a.先检查下所需字体Android是否提供
b.可以通过提取所需文字的ttf文件,大幅减小ttf体积
若需语音播报,我们会将音频文件放置assets。选用音频文件优先考虑MP3、AAC等有损格式而非PCM、WAV无损格式。有损和无损在语音质量方面差别甚微、但文件大小却是天差地别
若应用需要内嵌web,即应用在同一局域网内浏览器打开web和应用主程序实现交互场景。web端所需的某些元素可以存放在文件系统而非assets
如非必要,考虑是否可通过网络存储、文件存储等其他方式保存资源,视具体情况而定,原则上尽量不参与打包
-
lib。lib下存放各种架构的so文件,现在大部分机器的cpu架构是armv7,所以我们打包时只需加入以下代码过滤掉其他的abi(最终根据实际情况)。具体路径:app目录下的build.gradle
附上代码:ndk { abiFilters "armeabi-v7a" }
效果:优化前
效果:优化后
-
res。这里都是一些资源文件,包含mipmap、drawble、layout等资源文件。优化方向主要有两个:
使用第二种方式“shrinkResources“时需关注两点:
a.shrinkResources的使用一定要配合minifyEnabled,只有minifyEnabled为true时shrinkResources才会生效。具体原因就是我们的资源都是代码中使用的,我们需要知道哪些资源被代码使用,所以先得检测代码,即代码压缩/代码混淆
b.shrinkResources默认发现代码中调用资源R.mipmap.a,则其他未使用资源R.mipmap.a*也会被打包进apk(*代表任意字符或字符串),所以如果您想自定义要保留的资源,则在路径res/raw/keep.xml定义规则
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:discard="@color/selector_tint_color" tools:keep="@layout/activity_test1,@layout/activity_test2" tools:shrinkMode="strict"/> <!--discard:做严格检查--> <!--keep :不做严格检查--> <!--shrinkMode="strict" :该模式只保留在代码或者资源文件中明确引用的资源--> <!--shrinkMode="safe" :该模式会保留所有明确引用的资源以及可能被 Resources.getIdentifier() 动态引用的资源-->
注意:当使用lint删除资源文件时,对于代码中使用了getIdentifier(String name, String defType, String defPackage)设置图片,这就需要格外小心了。我们一般的做法就是用lint检测无用资源然后删除,但getIdentifier()函数并不直接引用资源文件,而是输入资源名称,从而引起“资源文件不存在“的报错
下表是我测试的一组对照数据,根据自己的经验总结:
PNG 2.3M(大图) 124K(小图) 原图 SVG(矢量) 1.44M 257K 有细微失真 WebP 91.5K 16.3K 没有失真 a.背景图选用webp比较划算
b.小图标选用svg比较划算
c.对于图标相同只是颜色不同的无需再做图,我们只需要设置着色器android:tint属性就好了,可以省下好些图标空间
值得提一句:我们写layout布局时,1.可以将公共部分提取出来,2.改用约束布局尽量少一些layout层级。虽然对包体积不是很大,但多多少少会影响一些性能
-
.dex,该目录是.java文件在Davlik编译的字节码文件,相当于JVM的字节码.class,优化方向有以下几点:
1.将app目录下build.gradle文件中minifyEnabled设置为true,即代码压缩/代码混淆。这里需要注意的是对于从外部引入的一些三方库,不能够被混淆,需要在proguard-rules定义混淆规则
2.移除废弃功能的代码,反正有Git,删了代码随时可以找回
3.提取重复代码
4.减少不必要的依赖。有时为了某个需求添加依赖,而后删除该需求后需记得删除该依赖
5.删除重叠框架。例如网络请求框架有okhttp、volley等,协同开发时往往会引入多种网络框架,这时只需保留一种框架
6.插件化。例如某些不常用的功能,既想拥有小体积,又想保留该功能,这时只需保留接口,通过网络从服务器下载相应功能的插件apk,在主应用中动态加载。关于插件化,我在后面的文章中会做简单介绍
-
.arsc。它是res的一套关系映射规则,通过以下这张图我们应该清楚知道平时我们代码中使用的R.xxx.xxx其实就是通过这个关系映射找到的
像以上的string资源,默认它会编译出所有语言,如无必要,我们只需编译所需语言即可。此处以中文为例,只需在app目录下build.gradle中加入:resConfigs ‘zh’
代码如下:resConfigs "zh"
优化后效果:
总结
1.优化方向大致就是以上几点。通过一系列的优化,应用从39.2M优化到了15.6M
2.关于优先级需要大家结合自身项目,根据优化难易程度、可优化幅度等多重因素综合定夺
拓展
1.assets、res之间的异同?
答:异:
- assets文件夹下的文件不会被映射到R.java中,res文件夹下的文件会被映射到R.java文件中
- assets访问需要AssetManager类,res访问直接使用资源ID
- assets可以自定义目录结构,res不可以自定义目录结构(以下情况例外,build.gradle声明)
同:两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制
2.META-INF有什么用?
答:META-INF文件夹主要存放的配置信息,签名信息和版本信息,不能在瘦身时删除
上图中可发现CERT.SF,MANIFEST.MF,CERT.RSA和一些依赖包的version:
CERT.SF:
MANIFEST.MF:存放的是版本号,以及每个文件的哈希值,内容形式跟CERT.SF基本一致
CERT.RSA:签名相关
工具
字体提取工具:1.FontCreator、2.sfnttool