为什么 APK 要瘦身。APK 越大,在下载安装过程中,他们耗费的流量会越多,安装等待时间也会越长;对于产品本身,意味着下载转化率会越低(因为竞品中,用户有更多机会选择那个体验最好,功能最多,性能最好,包最小的),所以 apk 的瘦身优化也很重要,本篇博客将讲述 apk 瘦身的相关内容。
在 Android Studio 工具栏里,打开 build–>Analyze APK, 选择要分析的 APK 包 。
可以看到占用空间的主要是代码、图片、资源和 lib 和 assert 文件,主要方向精简代码、压缩图片、去除无用的库、减少 asserts 里面文件。
对于绝大对数 APP 来说,只需要取一套设计图就足够了。鉴于现在分辨率的趋势,建议取 720p 的资源,放到 xhdpi 目录。
相对于多套资源,只使用 720P 的一套资源,在视觉上差别不大,很多大公司的产品也是如此,但却能显著的减少资源占用大小,顺便也能减轻设计师的出图工作量了。
在 gradle 使用 minifyEnabled 进行 Proguard 混淆的配置,可大大减小 APP 大小:
- android {
- buildTypes {
- release {
- minifyEnabled true
- }
- }
- }
在 proguard 中,是否保留符号表对 APP 的大小是有显著的影响的,可酌情不保留,但是建议尽量保留用于调试。
参数说明:
- - include {
- filename
- }从给定的文件中读取配置参数 - basedirectory {
- directoryname
- }指定基础目录为以后相对的档案名称 - injars {
- class_path
- }指定要处理的应用程序jar,
- war,
- ear和目录 - outjars {
- class_path
- }指定处理完后要输出的jar,
- war,
- ear和目录的名称 - libraryjars {
- classpath
- }指定要处理的应用程序jar,
- war,
- ear和目录所需要的程序库文件 - dontskipnonpubliclibraryclasses指定不去忽略非公共的库类。 - dontskipnonpubliclibraryclassmembers指定不去忽略包可见的库类的成员。
保留选项
- - keep {
- Modifier
- } {
- class_specification
- }保护指定的类文件和类的成员 - keepclassmembers {
- modifier
- } {
- class_specification
- }保护指定类的成员,如果此类受到保护他们会保护的更好 - keepclasseswithmembers {
- class_specification
- }保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。 - keepnames {
- class_specification
- }保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除) - keepclassmembernames {
- class_specification
- }保护指定的类的成员的名称(如果他们不会压缩步骤中删除) - keepclasseswithmembernames {
- class_specification
- }保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后) - printseeds {
- filename
- }列出类和类的成员 - keep选项的清单,标准输出到给定的文件
压缩
- - dontshrink不压缩输入的类文件 - printusage {
- filename
- } - whyareyoukeeping {
- class_specification
- }
优化
- -dontoptimize 不优化输入的类文件
- -assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用
- -allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员
混淆
- - dontobfuscate不混淆输入的类文件 - printmapping {
- filename
- } - applymapping {
- filename
- }重用映射增加混淆 - obfuscationdictionary {
- filename
- }使用给定文件中的关键字作为要混淆方法的名称 - overloadaggressively混淆时应用侵入式重载 - useuniqueclassmembernames确定统一的混淆类的成员名称来增加混淆 - flattenpackagehierarchy {
- package_name
- }重新包装所有重命名的包并放在给定的单一包中 - repackageclass {
- package_name
- }重新包装所有重命名的类文件中放在给定的单一包中 - dontusemixedcaseclassnames混淆时不会产生形形色色的类名 - keepattributes {
- attribute_name,
- ...
- }保护给定的可选属性,例如LineNumberTable,
- LocalVariableTable,
- SourceFile,
- Deprecated,
- Synthetic,
- Signature,
- and InnerClasses. - renamesourcefileattribute {
- string
- }设置源文件中给定的字符串常量
在 gradle 使用 shrinkResources 去除无用资源,效果非常好。
- android {
- buildTypes {
- release {
- shrinkResources true
- }
- }
- }
版本迭代过程中,不但有废弃代码冗余,肯定会有无用的图片存在。在 build.gradle 里面配置 shrinkResources true,在打包的时候会自动清除掉无用的资源,但经过实验发现打出的包并不会,而是会把部分无用资源用更小的东西代替掉。注意,这里的 "无用" 是指调用图片的所有父级函数最终是废弃代码,而 shrinkResources true 只能去除没有任何父函数调用的情况,真正起效果只能通过 Android Studio 自带的 "Remove Unused Resources" 小插件来实现了,直接上图。
更人性化是该查找结果可以 "一键删除"。当然,可能图片是经过反射或字符拼接等方式获取,所以这个检测列表也不是全对,删除后很大概率编译失败或部分页面挂死、无图等问题,这个无解,工具还没智能到这个地步,你只能一遍又一遍 "编译—解决部分问题—再编译再",别问我为什么知道。
大部分应用其实并不需要支持几十种语言的国际化支持。还好强大的 gradle 支持语言的配置,比如国内应用只支持中文:
- android {
- defaultConfig {
- resConfigs "zh"
- }
- }
TinyPNG 工具只支持上传 PNG 图片到官网上压缩,然后下载保存,在保持 alpha 通道的情况下对 PNG 的压缩可以达到 1/3 之内,而且用肉眼基本上分辨不出压缩的损失.
Tinypng 的官方网站:
如果对于非透明的大图,jpg 将会比 png 的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。
在启动页,活动页等之类的大图展示区采用 jpg 将是非常明智的选择。
webp 支持透明度,压缩比比 jpg 更高但显示效果却不输于 jpg,官方评测 quality 参数等于 75 均衡最佳。
相对于 jpg、png,webp 作为一种新的图片格式,限于 android 的支持情况暂时还没用在手机端广泛应用起来。从 Android 4.0 + 开始原生支持,但是不支持包含透明度,直到 Android 4.2.1 + 才支持显示含透明度的 webp,使用的时候要特别注意。
官方介绍:
如果经过上述步骤之后,你的工程里面还有一些大图,考虑是否有必要维持这样的大尺寸,是否能适当的缩小。
事实上,由于设计师出图的原因,我们拿到的很多图片完全可以适当的缩小而对视觉影响是极小的。
基本上 armable 的 so 也是兼容 armable-v7 的,armable-v7a 的库会对图形渲染方面有很大的改进,如果没有这方面的要求,可以精简。
这里不排除有极少数设备会 Crash,可能和不同的 so 有一定的关系,请大家务必测试周全后再发布。
与第十条不同的是,x86 包下的 so 在 x86 型号的手机是需要的,如果产品没用这方面的要求也可以精简。
建议实际工作的配置是只保留 armable、armable-x86 下的 so 文件,算是一个折中的方案。
微信资源压缩打包工具通过短资源名称,采用7zip 对 APP 进行极致压缩实现减小 APP 的目标,效果非常的好,强烈推荐。
建议开启 7zip,注意白名单的配置,否则会导致有些资源找不到,官方已经发布 AndResGuard 到 gradle 中了,非常方便:
- apply plugin: 'AndResGuard'
- buildscript {
- dependencies {
- classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.1.7'
- }
- }
- andResGuard {
- mappingFile = null
- use7zip = true
- useSign = true
- keepRoot = false
- // add <your_application_id>.R.drawable.icon into whitelist.
- // because the launcher will get thgge icon with his name
- def packageName = <your_application_id>
- whiteList = [
- //for your icon
- packageName + ".R.drawable.icon",
- //for fabric
- packageName + ".R.string.com.crashlytics.*",
- //for umeng update
- packageName + ".R.string.umeng*",
- packageName + ".R.string.UM*",
- packageName + ".R.string.tb_*",
- packageName + ".R.layout.umeng*",
- packageName + ".R.layout.tb_*",
- packageName + ".R.drawable.umeng*",
- packageName + ".R.drawable.tb_*",
- packageName + ".R.anim.umeng*",
- packageName + ".R.color.umeng*",
- packageName + ".R.color.tb_*",
- packageName + ".R.style.*UM*",
- packageName + ".R.style.umeng*",
- packageName + ".R.id.umeng*"
- ]
- compressFilePattern = [
- "*.png",
- "*.jpg",
- "*.jpeg",
- "*.gif",
- "resources.arsc"
- ]
- sevenzip {
- artifact = 'com.tencent.mm:SevenZip:1.1.7'
- //path = "/usr/local/bin/7za"
- }
- }
会生成一个 andresguard/resguard 的 Task,自动读取 release 签名进行重新混淆打包。
对于一些库是按照需要动态的加载,可能在某些版本并不需要,但是代码又不方便去除否则会编译不过。
使用 provided 可以保证代码编译通过,但是实际打包中并不引用此第三方库,实现了控制 APP 大小的目标。 但是也同时就需要开发者自己判断不引用这个第三方库时就不要执行到相关的代码,避免 APP 崩溃。
矢量图是由点与线组成, 和位图不一样, 它再放大也能保持清晰度,而且使用矢量图比位图设计方案能节约 30~40% 的空间,现在谷歌一直在强调扁平化方式,矢量图可很好的契合该设计理念。
—优势
(1) 占用存储空间小
(2) 无极拉伸不会出现锯齿,可以照顾不同尺寸的机型
(3)Android Studio 自带很多资源,减小 UI 工作量
—劣势
(1) 只支持 5.0 及以上系统
(2) 与位图相比多了一层计算,需消耗更多性能
(3) 不支持. 9 图
(4) 不适合表现真实照片和复杂图形,一般使用在简单的 icon 和动画上
相信你的工程里也有很多 selector 文件,也有很多相似的图片只是颜色不同,通过着色方案我们能大大减轻这样的工作量,减少这样的文件。
借助于 android support 库可实现一个全版本兼容的着色方案,参考代码:DrawableLess.java
如果你的 APP 支持素材库 (比如聊天表情库) 的话,考虑在线加载模式,因为往往素材库都有不小的体积。这一步需要开发者实现在线加载,一方面增加代码的复杂度,一方面提高了 APP 的流量消耗,建议酌情择。
避免重复库看上去是理所当然的,但是秘密总是藏的很深,一定要当心你引用的第三方库又引用了哪个第三方库,这就很容易出现功能重复的库了,比如使用了两个图片加载库:Glide 和 Picasso。 通过查看 exploded-aar 目录和 External Libraries 或者反编译生成的 APK,尽量避免重复库的大小,减小 APP 大小。
版本迭代过程中,因为删减功能经常有冗余代码和第三方库留下,这或多或少都会增加包体,这种情况没有捷径,只能每个文件查找,这是苦力活。还有要查看第三方库有没可能精简,比如谷歌分基础、广告和分析包,网络库、supportv4 等,这个就具体情况具体分析,不多阐述。
插件化技术支持动态的加载代码和动态的加载资源,把 APP 的一部分分离出来了,对于业务庞大的项目来说非常有用,极大的分解了 APP 大小。
因为插件化技术需要一定的技术保障和服务端系统支持,有一定的风险,如无必要(比如一些小型项目,也没什么扩展业务)就不需要了,建议酌情选择。
redex 是 facebook 发布的一款 android 字节码的优化工具,需要按照说明文档自行配置一下。
- redex input.apk -o output.apk --sign -s <KEYSTORE> -a <KEYALIAS> -p <KEYPASS>
下面我们来看看它的效果,仅 redex 的话,减小了 157k:
下面我们来看看它的效果,仅 redex 的话,减小了 157k:
如果先进行微信混淆,再 redex,减小了 565k,redex 只贡献了 10k:
如果先进行 redex,在进行微信混淆,减小了 711k,redex 贡献了 157k:
最后一种的效果是最好的,这是很容易解释的,如果最后是 redex 的重新打包则浪费了前面的 7zip 压缩,所以为了最优效果要注意顺序。
另外,据反应 redex 后会有崩溃的现象,这个要留意一下,我这里压缩之后都是可以正常运行的。
详情参考:ReDex
来源: