京东到家Android瘦身探索
一、背景
二、分析现状
图 中 各个目录作用如下图:
从上图中我们可以清晰看出lib、res、assets和class.dex占了很大一部分的比重,直接影响了包的体积大小,接下来围绕这几个方面我们将着重介绍采取的瘦身及包体积监控方案。
三、瘦身方案
3.1 lib瘦身
3.1.1 无用so文件剔除
ndk { abiFilters "armeabi"或"arm64-v8a" }
-
应用市场:通过双包上传至应用市场平台,平台会根据用户手机系统下载对应APK文件; -
APP站内升级:获取手机CPU支持集下载对应APK文件;
3.2.1 图片资源
图片使用规范:
-
图片适配:Android为了适配不同分辨率的设备在资源目录下通常会存放1X、2X、3X等图片资源,但事实上我们只需要选择1X或者2X即可。 -
图片格式优化:总体上遵循无透明通道使用JPG,优先使用Webp以及通过.9和矢量图实现图片拉伸替代大图。 -
着色方案:对于一些颜色单一的图片通过代码去实现替代图片。
压缩配置策略:
//自动在线压缩图片配置项TinyPngInfo{//api_key 配置多个,随机取配置的api_key,api_key=["xxxxxxx","xxxxxxx","xxxxxxx"]//待压缩的modulecheckModuleName=['xx']//忽略压缩图片的数组白名单excludeImage=['xx.png']//最小压缩的图片大小,单位是KBcheckResMinSize=0//指定需要压缩的图片数组compressImage=[]//指定具体目录,正常情况不需要配置该选项checkResDir=["src${File.separator}main${File.separator}assets","src${File.separator}main${File.separator}res"]}
脚本流程图:
执行结果:
-
通过混淆资源ID长度减小了APK包大小,例如将res/drawable/hello.png混淆为r/s/a.png,并将映射关系输出到mapping文件中; -
混淆后在安全性方面有一点提升,提高了逆向破解难度;
实现原理参照下图:
通过该工具res目录从5.7M瘦身到4.7M,占res资源17%,瘦身效果显著。
3.2.3 APK压缩格式优化
/* these formats are already compressed, or don't compress well */static const char* kNoCompressExt[] = {".jpg", ".jpeg", ".png", ".gif",".wav", ".mp2", ".mp3", ".ogg", ".aac",".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",".rtttl", ".imy", ".xmf", ".mp4", ".m4a",".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"};
由于项目的快速迭代以及模块化开发,项目中不可避免的出现了大量的重复以及无用资源;通过调研我们采用了腾讯开源的ApkChecker。它是如何排查重复和无用资源呢?
-
针对重复资源,它是通过遍历解压后的所有文件夹,对每一个文件内容求MD5值,MD5值相同即为重复文件。 -
针对无用资源,简单来说它会通过读取R.txt获取所有的资源,然后遍历所有的资源文件探寻引用关系,最终排查出无用的资源。
3.3 类文件优化
-
保持良好的代码习惯,慎用第三方库,选择体积较小的第三方库,剔除掉重复以及无用的代码(注意一定要case by case )。 -
开启ProGuard来进行类文件压缩,开启之后便可以对代码进行混淆、优化、压缩等工作。
-
代码缩减(即摇树优化):从应用及其库依赖项中检测并安全地移除不使用的类、字段、方法和属性(这使其成为了一个对于规避 64k 引用限制非常有用的工具)。例如,如果您仅使用某个库依赖项的少数几个 API,那么缩减功能可以识别应用不使用的库代码并仅从应用中移除这部分代码。如需了解详情,请参照文末文献。 -
从封装应用中移除不使用的资源,包括应用库依赖项中不使用的资源。此功能可与代码缩减功能结合使用,这样一来,移除不使用的代码后,也可以安全地移除不再引用的所有资源。如需了解详情,请参照文末文献。 -
缩短类和成员的名称,从而减小dex文件的大小。如需了解详情,请参照文末文献。 -
优化:检查并重写代码,以进一步减小应用的dex文件的大小。
android{...buildTypes {release {// 开启混淆minifyEnabled true// 开启资源压缩,编译时会自动删除未使用到的res资源shrinkResources trueproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}debug {//开启混淆会导致编译速度变慢,debug通常不开启minifyEnabled falseshrinkResources falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}}
-
jni方法不可混淆,方法名需与native方法保持一致; -
反射用到的类不混淆,否则反射可能出问题; -
四大组件、Application子类、Framework层下的类、自定义的View默认不会被混淆,无需另外 配置。 -
WebView的JS调用接口方法不可混淆;
-
注解相关的类不混淆;
-
Bean数据类不可混淆;
-
枚举enum类中的values和valuesof这两个方法不可混淆(反射调用);
-
继承Parceable和Serializable等可序列化的类不可混淆;
-
第三方库或SDK,请参考第三方提供的混淆规则,没提供的话,建议第三方包全部不混淆;
四、如何瘦身不反弹
-
多维度监控:支持单个文件超限配置、模块文件增量超限配置、APK整体增量超限配置三个维度的版本增量控制。 -
结果呈现直观:对比结果从整体、新增、删除、变大、变小五个维度直观排列,更易排查版本增量问题。
使用方式:
allResCheckInfo {remoteVersion = '1.1.0' //需要对比的版本versionName = '1.0.0' //本地版本warnSize = 5 //单个文件超出提示outPutsAddSize = 100 //每期增量最大值,超出警告outPutsSize = 50000 //产物最大值,超出警告sortAllFileSize = true //是否对所有文件排序check_projectNames=['app','jni'] //待检查的module名字outPutsFileName = "" //产物名称projectName = '' //工程名字check_userName = '****'//推送到服务端的账号check_passWord = '****'//推送到服务端密码serviceUrl="http://****"isOpenPushRemote = true //是否开启推送到远程,用于版本对比}
工作流程如下图:
五、成果与后续规划
5.1 瘦身成果
5.2 后续规划
-
一键压缩工具2.0版:在日常的使用中我们发现压缩工具在以下两点仍有提升的空间。第一点我们只能压缩项目res目录下的图片资源,但是对第三方SDK目录下的资源无能为力;第二点仍旧需要手动执行压缩任务不够便捷。针对这两点我们在分析APK构建流程中发现所有图片最终会打进APK的res目录下,因此我们将以此为突破口实现更加好用的工具2.0。
-
资源转下载:项目中的一些本地lottie动画及文件资源在项目中仍占据较大的空间,接下来会将部分资源从APK中剔除,采用动态下载的方式减少原文件大小。 -
R文件瘦身:APK编译时会生成resource.arsc文件,应用层需通过访问R文件中的ID引用对应资源;现通过将ID内联到程序中不再依赖R文件,将APK中的R文件删除达到瘦身效果。