(转)Android-Compress

  • 2017-09-10
  • 112
  • 0

本文由 简悦 SimpRead 转码, 原文地址 https://zhuanlan.zhihu.com/p/23882195?utm_medium=social&utm_source=qq

1、我是怎么思考这件事情的

APK 是 Android 系统安装包的文件格式,关于这个话题其实是一个老生常谈的题目,不论是公司内部,还是外部网络,前人前辈已经总结出很多方法和规律。不过随着移动端技术近两年的飞速发展,一些新的思维方式和优化方法也逐渐涌现和成熟起来。笔者在实践过程中踩过一些坑,收获了一些经验,在这里做个思考和总结,所以随笔给大家,希望对大家从事相关工作的时候有所帮助和参考,同时也是抛砖引玉,希望大家共同探讨这个开放性的话题。

关于为什么 APK 要瘦身,这个不多说,只从三个方面唠叨一下,对于用户(或者客户)来说,APK 越大,在下载安装过程中,他们耗费的流量会越多,安装等待时间也会越长;对于产品本身,意味着下载转化率会越低(因为竞品中,用户有更多机会选择那个体验最好,功能最多,性能最好,包最小的);对于研发来说,是一种优化改进技术的机会。

欲瘦身,我们先找找胖的原因和问题。按目标-路径-资源的思维模式,找原因和问题有如下几条路径,一是拍脑袋,按自己的经验和判断,甚至是主观想象;二是去搜索引擎找关键字,逛各种技术论坛听技术大牛们怎么说,看各类技术文章抽取提炼;三是用一种可测量的工具或者方法发现问题。

前两种不赘述,我这里说说第三种方法。用一种可测量的工具或者方法来分析,所谓工欲善其事,必先利其器。这个器可以可以自己锻造,也可以用现成的。这里推荐一个在线 apk 分析工具,因为是外部工具,所以大家请在使用过程中,不要上传未发布出去的产品,为了数据安全,笔者这里拿一个 github 上开源的 Android 项目作为瘦身示例。

2、寻找问题

NimbleDroid 是美国哥伦比亚大学的博士创业团队研发出来的分析 Android app 性能指标的系统,分析的方式有静态和动态两种方式,其中静态分析可以分析出 APK 安装包中大文件排行榜,各种知名 SDK 的大小以及占代码整体的比例,各种类型文件的大小以及占排行,各种知名 SDK 的方法数以及占所有 dex 中方法数的比例,废话不多说,下面上高清无码大图看看颜值吧。

如果想使用分析功能分析自己的产品,请登录并上传自己产品的 apk 包,所有功能目前均免费使用,如果是想分析 Google Play 上已经发布的产品,可以直接点击 "Play Apps" 查看,还可以使用搜索功能根据应用名和包名查看结果。再次强调下,请不要上传任何未发布的产品。

登录

上传 apk 文件

分析结果摘要,可以看到一些概览的信息,apk 文件大小,总的方法数

文件大小分析详情页,大文件列表,这里列出的是 apk 文件中超过 100k 的文件排行,这里的文件大小指的是 apk 文件中的大小

各种知名 SDK 的大小以及占代码整体的比例,这里目前能识别出 Android Support,Jackson JSON parser, Google Play Services, Paypal, Glide, OkHttp, Facebook SDK, Fabric, Gson 等等,Application 表示 App 中自己编写的代码部分

各种类型文件的大小以及排行

各种知名 SDK 占所有 dex 中方法数的比例

各种知名 SDK 的方法数排行榜

看完这个 apk 内剖图是不是有一种神清气爽的感觉!我把这个分析工具比做我们家买的智能体重秤,可以称体重,脂肪含量,骨重,骨密度,肌肉含量等等,那么,我们是不是发现了一些问题,进而把这些问题和我们之前靠经验和一拍脑袋的原因可以用逻辑联系在一起。

那么,我们接下来可以通过分析数据整理出我们的优化目标

  1. 大文件排行榜里,有 11 张 png 文件的大小超过了 100k,记住,这可是压缩之后的啊;
  2. 大文件排行榜里,resources.arsc 的大小接近 2M,这也是一个优化点;

  3. 大文件排行榜里,classes.dex 接近 3M,classes.dex 是代码的载体,这块的优化需要细分,再去看看细分 SDK 的排行榜;

  4. 组件占比环图里,Android Support, Jackson JSON Parser 和 Google Play Services 是三方库的前三甲;

  5. 文件类型排行榜里,png, dex 和 arsc 是前三甲;

3、梳理优化目标

所以我们的目标是没有蛀牙,不对,是下面的目标:

  1. png 图片优化;
  2. resources.arsc 文件的优化;

  3. 代码优化

3.1 图片优化的尝试

首先是第一个目标,图片的优化,慢点,我们看看这些图为什么这么大先,准确的说,为什么这些图在 apk(其实就是 zip 文件)里这么大,好了,上工具分析。

这次用了一些简单的工具组合,系统自带的 cmd 就好。

命令执行的结果如下

恩,所有的 png 文件居然是 STORE 的方式存储到 apk 里的,关于 zip 里的 STORE 和 DEFLATE,详见 )

通俗的说,当文件是 STORED 的方式存储到 zip,表示这个文件并没有经过压缩,如果是 Defl:N 的方式,表示通过 DEFLATED normal 的方式压缩存储到 zip。

这看起来有点不合理,png 原封不动的放入 zip,当然最后产出的 apk 会比较大。那么,如何解决呢?笔者首先尝使用 android gradle plugin 的方式,发现 aaptOptions 和 packagingOptions 都未能解决问题。在 github 上发现一个开源项目 AndResGuard,试了集成到项目中,再看结果如下:

优化前:

10536027 字节

优化后:

普通 zip 压缩: 8786265 字节 (压缩了将近 17%)

采用 7zip 压缩:8567150 字节 (压缩了将近 19%)

再看看这个工具做了什么,对比下开启资源混淆前后

优化前

优化后

  1. 资源(png, xml, jpg 等)名称混淆,资源路径名称混淆以及名称长度压缩;
  2. 原来以 STORED 形式存储到 zip 中的 png 文件被改成了 DEFLATED(普通压缩存储)方式;

  3. 意外发现 resources.arsc, META-INF/.SF 以及 META-INF/.MF 变小了,而且是解压之后的文件大小也变小了。

用 apk 反编译神器 jadx内窥 apk 寻找真相

原来 apk 中资源(png, xml,以及 properties 文件)的相对路径会存放到 META-INF/.SF 以及 META-INF/.MF 中并为每个资源文件计算 SHA1 值并存储在这两个文件中,至于为啥这么做以及这两个 SHA1 有啥区别和作用请参考网络上关于这方面知识的文章,已超出本文的主题所以这里不再赘述。

对于 resources.arsc 文件

很容易看出来它是资源文件索引表,所以,看到这里大家应该明白这三个文件为啥会变小了吧。

3.2 一次意外的发现

顺着 resources.arsc 往下看,发现一个有趣的东西,

这又将成为一个优化点,去除那些没用的翻译资源,引入一些第三方的 SDK,往往这些 SDK 带了很多翻译资源在里面,比如 android support 库,去掉后我们来看看效果。

假设我们只保留英文,当然只是个实验,现实中看具体情况了,

采用 7zip 压缩:8220738 字节 (压缩了将近 22%,再增加 3 个点)

当然,真实的项目里不可能这样,但是蚊子肉也是肉啊!

其实,我想说的是这提供了一种优化思路,就是利用 gradle 的配置干掉无用的资源,同样的可以用在 so 本地库上,分辨率(gradle 配置已 deprecated)上。

gradle 配置示例如下:

记得包在 android{} 中间哦。那么,有人要问了,abi 里肿么没有 x86?据说 intel 提供了一个解决方案叫 houdini,是一个运行在 x86 设备上的中间件,可以将 arm 转码为 x86 的指令,不过效率很低,有些运算型的,比如计算 MD5 和 SHA1,甚至不如 java,笔者曾经做过测试对比,又是另外一个话题,此处不赘述,感兴趣的读者可以移步。

到此为止,我们已经在朝第一个目标迈进,不经意间发现了第一个目标和第二个目标之间的关系,所以利用资源混淆工具,达成了第二个目标。

利用 7zip 压缩,我们对整个包进行了 2 个点的压缩,这是一个超出预期的成果。

3.3 图片优化的方法

关于第一个目标,我们的路径还没有结束,拍脑袋想出来的路径是压缩 png,非 alpha 图转成 jpg,还有什么?所以去各种技术论坛逛了一圈,请教了各种技术大牛,梳理的路径如下:

1、手动 lint 检查,手动删除代码中没有引用到的资源,实际效果不等。

在 Android Studio 中打开 “Analyze” 然后选择 "Inspect Code...",范围选择整个项目,然后点击 "OK"

配置如下图

2、gradle 脚本中开启 shrinkResources

脚本参考如下

shrinkResources 配合 minifyEnabled 使用效果更佳,详见 shrinkResources 用法以及注意

采用 7zip 压缩:8115283 字节 (压缩了将近 23%,再增加 1 个点)

3、使用图片压缩工具,压缩 png 图的大小,将非 alpha 的图转换成 jpg 形式,关于这点同事以及网络上的大牛们已经整理的很详细了,我这里做简单总结,欲知详情,请见附录的参考。

使用 tinypng,我只想说咱们在公司做产品,此方案慎用,上传任何未发布产品的内容到外部网络,都有可能引起数据泄漏,所以慎用此方案。下面说替代方案。

以上工具太散,有没有集成化的工具,答案是 “有”

@心伦 童鞋开发的 imagemin

@姐夫童鞋开发的 MSImageResourcesHelper

png 转成 jpg 格式,具体效果不等。

4、终极大杀器,png 转成 webp,关于 webp,更多详情请参考谷歌官方文档以及安卓开发者在线参考

先上效果图:

采用 7zip 压缩:4926912 字节 (压缩了将近 53%,再增加 30 个点)

没看错吧,是 30 个点,目前 apk 的大小是原始 apk 大小的一半不到,而我做的,一行代码木有改动,仅用了一些工具而已!

说人话,我木有吃减肥药,木有绝食,体重却轻了一半!!!

但是,目前却没能用到项目中,因为有两个坑

  • 在三星的部分机型上,部分有 alpha 背景部分的图会有一条很明显的黑线,这里就不上图了,这个问题目前通过白名单的方式不去做成 webp 的图来处理;
  • 在小米 2 刷成 4.xx 的手机上,未能正确识别 xml 文件中描述的 webp 图片,导致界面起来后加载 xml 布局文件,文件加载 webp 失败,报错说 resource file not found,导致 app 发生崩溃。跟踪发现是小米机器代理了类 Resource 为 MIUIResource,但是这个 MIUIResource 未能正确识别 webp,所以导致加载资源文件失败,初步判定,目前暂时没有解决方案,所以只能忍痛放弃这个优化方案。

关于第一个目标,图片资源的优化,就写到这里了。

3.4 代码优化

第二个目标已达成,剩下第三个目标,代码的优化,梳理如下优化路径:

1、开启 proguard 的代码优化

proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-project.txt'

改为

proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),'proguard-project.txt'

开启代码优化后的注意点请参见附录。

2、去除无用的库

如果 apk 支持的最低版本是 API14, 而代码中没有用到高于 api14 的 api 就可以考虑拿掉整个 android support 库。

3、用更小的库替代方案

如果只用到了谷歌统计,那么就不要把整个 google play services 都集成进来,只集成需要的部分。

4、定期清理废弃的代码

定期删除无用的逻辑和过期的业务功能模块,以及废弃的 A/B test 代码。

5、业务模块采用插件化框架,代码动态从云端拉取

插件化,这是另外一个课题了,这里不赘述。

apk 瘦身记最终的成果

10536027 字节压缩到 4926912 字节****, 压缩了将近 53%

总结

  1. 脚本中开启资源混淆和资源压缩
  2. 用 7zip 代替 zip

  3. gradle 脚本中开启代码混淆优化和无用资源删除

  4. 用更小的图,使用压缩工具压缩图片大小

  5. 去除无用的资源,语言,本地 so 库,二方三方库和分辨率

  6. 用更小的库

  7. 尝试将 android support 库彻底踢出你的项目

  8. 定期清理代码

  9. 尝试用 H5 编写界面,图片云端获取

  10. 尝试插件化业务模块

  11. 寻找到 zip 文件夹中所有用 STORE 形式存储的文件(不限于 raw 目录下),尝试压缩,以及替代方案加载这些资源

  12. 尝试 webp 的图片加载方案,寻求突破

最后,继续学习和尝试新的优化方案

以此文献给 “唯瘦身与产品不可辜负” 的技术们!!!

附录

如何做到将 apk 大小减少 6M

Android APP 终极瘦身指南

APK 瘦身实践

作者:非戈 @阿里移动安全

评论

还没有任何评论,你来说两句吧