来自微信团队的Apk瘦身经验

移动开发 Android
随着版本迭代升级,Android安装包越来越大,一些内存将满的手机会出现无法安装的现象。一篇来自微信团队的文章,介绍了安装包的生成、编译过程、减少安装包大小的Tips以及如何判断安装包是否可以安装等。

用户抱怨安装包越来越大?印度友人反馈装不上微信?欢迎来到本期的走进科学--安装包速成记。做一个有节操的安装包,我们希望它越小越好,并且确保用户都能安装的上。

Android的安装包,简单来说就是一个压缩包,首先我们了解一下它的生成过程。

一、安装包编译过程

一般我们使用ant、gradle等方式编译生成安装包,它一般包含以下几个步骤。

但是对于多library的结构下,ant与gradle的并行度并不足够。当前微信已经切换到Facebook的开源编译工具buck(相关介绍http://facebook.github.io/buck/),编译速度得到了大大提升。

在buck的基础上我们主要做了以下两个工作:

EclipseToBuck,开发者无须关心buck脚本的编写,buck脚本使用代码自动生成

微信使用多library结构,每次编译后监控每个模块的线性内存、方法数、资源大小。

二、减少安装包大小的Tips

现在我们开始***项主要内容,如何减少安装包的体积?

1. 安装包监控

我们做安装包优化,首先要对我们安装包每一部分有一个详细的了解,知道安装包大在什么地方,与上一版本有着怎么样的变化。所以我们首先实现了一个安装包检测工具:

监控

1. 每个dex方法数的变更情况;

2. 每个模块线性内存的变化情况;

3. 没有alpha通道的png图,可压缩成jpg减少体积;

4. 超过一定数值的大文件,特别是图片资源可采用有损压缩;

5. 安装包的大小、文件数变化;

6. 新增文件、减少文件,文件大小发生变化的情况;

现时微信的安装包监控包括两个维度,一是每日的监控,采用的是***一个包与昨日的***一个包对比;二是版本之间的对比,发布前需要用待上线版本与线上版本对比。我们希望若发现问题,能立刻警报,提交到bug系统。

2. 删除无用资源

在产品的大锤下,每个模块不修改个三五十次,都不好意思说自己是微信的开发。在不停的迭代中,或多或少会出现无用的资源,包括但不限于xml、png、id、string。

查找无用资源主要使用lint的UnusedResources以及UnusedIds两个检查规则,但是针对多library结构,官方的lint在某些方面不符合我们的要求,所以我们修改了一些地方。

对于使用gradle的童鞋,可以采用:

  1. android { 
  2. ... 
  3. buildTypes { 
  4. release { 
  5. minifyEnabled true 
  6. shrinkResources true 
  7. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 

3. 图片处理

.png、.9.png、.jpg、.gif资源是每一个安装包永远的痛,追求美感的设计师希望每个像素都是***的。这边的tip有以下几点:

详细描述

1.对于体积特别大(超过50k)的图片资源可以考虑有损压缩,jpg采用优图压缩,png尝试采用pngquant压缩,输出视觉判断是否可行;

2.对assets中的图片资源也使用aapt的crunch做图片预处理;

3.crunch有可能会使图片变大,在这种情况,我们可以替换成原图。需要注意的是对于.9.png,由于crunch过程中去除了黑边,所以不能替换;

4.对于没有透明区域的png图片,可以转成jpg格式。

4. 字符串编码

为了节省空间,resources.arsc中的会有一个去重过的字符串资源池(相同的两个字符串其实用的是同一份),每个String使用偏移值来获取资源池中的数值。ResourceTable的编码对于resources.arsc的体积有很大影响。

从Android 2.2 API Level8开始APK文件的资源resources.arsc的编码有了小幅的改变,过去使用的是UTF-16编码方式被转换成了UTF-8编码。这样的好处就是处理纯英文等直接通过ascii存储语言的国家资源文件将会更小,而对于中文、日文这些国家的资源文件有可能会变大。

bool getUTF16StringsOption() {

return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO);

}

当然如果你发现使用UTF-8后resources.arsc反而变大,你可以强制使用UTF-16编码。只需要在aapt中指定--utf16参数,也就是指定mWantUTF16为true(下面的注释似乎跟代码有出入)。

" --utf16\n"

" changes default encoding for resources to UTF-16. Only useful when API\n"

" level is set to 7 or higher where the default encoding is UTF-8.\n"

微信5.2.1把minSdkVersion更改到8,使用utf8编码后,resources.arsc减少了将近1M。

5. 指定文件的压缩方式与7zip压缩

安装包是一个压缩文件,我们可以指定里面的文件采用哪种压缩方式。假若我们输入下面的命令:

aapt l <file_path.apk>

参数:

-v:会以table的形式输出目录,table的表目有:Length、Method、Size、Ratio、Date、Time、CRC-32、Name。

其中Method表示压缩形式,有:Deflate及Stored两种,即该Zip目录采用的算法是压缩模式还是存储模式;可以看出resources.arsc、*.png采用存储模式,而其它采用压缩模式。

有时候我们为了把某个数据文件不压缩,而把它的后缀名改成png(其实可以指定appt参数 -0 )。其实aapt对于某些压缩率不会太高的文件都默认使用了Stored模式。具体如下:

/* 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"};

文件如果采用Deflate方式存储,意味着通过AssetManager读取时需要解压,耗费的时间与文件的压缩比成比例。但大家记得这里面有一个坑,在Android 2.3以前的任何压缩的资源的原始大小超过1M,AssetManger读取时会抛出异常。这里面的Tip有:

Tips

1. 对.png、.jpg强制压缩,但是的确普遍压缩率在3%-5%,收益不是特别高;

2. 假若你的resources.arsc小于1M,可以对它进行压缩,这边可压缩50-70%。需要注意的是,如果你的resources.arsc是压缩的,程序需要把它读到内存,也就是会增大运行内存;

3. 对于安装包中的jar可以指定不压缩,其实二次压缩的压缩率非常低;

此外,7z压缩算法号称优化了字典,完全兼容zip,使用7zip的***压缩模式比传统zip方式的确有所提升,但是这里需要注意两个问题:

上文kNoCompressExt说到的流媒体文件,是通过mediaplay直接拿文件句柄(没有像assetmanager会先解压文件,要求文件是seekable)也不能压缩。

那么说一般来说只有.jpg、.jpeg、.png、.gif可以压缩,对于不需要兼容低版本或者resources.arsc小于1M的apk,可选择压缩resources.arsc。

6. C++的So库

5.1. C++运行时库统一使用stlport_shared

之前微信中的C++运行库大多使用静态编译方式,使用stlport_shared方式可减小APK包大小,相当于把大家公有的代码提取出来放一份,减少冗余。同时也会节省一点内存,加载so的时候动态库只会加载一次,静态库则随着so的加载被加载多份内存映像。

5.2. 把公用的C++模块抽成功能库

其实与上面的思路是一致的,主要为了减少冗余模块。大家都用到的一些基础功能,应该抽成基础模块。

7. 语言包动态加载

由于微信是一个国际化软件,我们在String中添加了20多种语言支持。这也导致我们的resources.arsc有5M多之巨,尝试过假如只留下默认的中文,resources.arsc可减少超过一半。事实上,大多数的语言我们并没有使用到,这里提到的一个思路是动态下发语言包,程序中继承Resource实现getString方式读取即可。

假若你的apk并不要求联网,要求用户动态下发语言包似乎不work。这里在研究buck编译的时候看到另外一个思路,即把大部分的语言二进制存放在assets。这个由于是普通数据文件,采用Deflate压缩方式,会有比较大的压缩率(与数据存储方式也有关系)。

8. 资源混淆

我们常常用proguard来混淆代码,那我们有没有想过资源是否可以混淆?而混淆资源能带来什么样的好处?

建议

1. 比较酷,让反编译的人更加难受一下。面对一大堆以a,b,c,d命名的png、xml;

2. 由于resources.arsc需要记录id与name的键值对,资源混淆对减少安装包体积也有帮助。具体数值与编码方式、id数量、平均减少命名长度有关(其实可以自己估量一下)。

微信的资源混淆工具不依赖源码,只要输入一个apk就能得到一个混淆之后的apk,大家有兴趣的话,可以后续单独来讲。通过资源混淆微信大约能减少1M左右的大小,效果如下:

9. 持续交付

持续交互的***目标就是让使用某个功能的人才会真正去下载某个模块,这首先需要我们代码结构的支持,即模块化以及支持动态加载。微信对gpserver、打飞机等功能试用动态加载方式。其实这块的核心思想就是将一些边缘的,不常用的功能,都尽量采用这种方式加载。但需要与产品大大们激烈PK,用户至上嘛。

记住,砍功能永远是减少安装包的***法宝!

三、安装包是否能安装

这里讨论第二个主要问题,如何确保我们的安装包用户都能安装的上,特别是2.3以下的爷们(希望有不需要考虑它们的一天)。

1. Dex的65536高压线

原因你懂得,超过请减少方法数,或自行拆多dex。其实在这里,我们这边有一点积累,但不在这篇文章的讨论范围。具体可参考km中的一些文章:

计算某个dex的方法总数,可使用:

function dex-method-count() {

cat $1 | head -c 92 | tail -c 4 | hexdump -e '1/4 "%d\n"'

}

想看到所有的方法,用dexdump吧。

2. 任何压缩的资源的原始大小不能超过1M

具体参考减少安装包大小tips中的第五点。

3. 线性内存限制

其实这个往往才是决定安装包是否能安装的上的阿喀琉斯之踵(用完这个名词,顿时高大上)。

关于线性内存的限制,可参考文章:

https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920

简单来说就是在dexopt过程中,系统限制一个5-16M的线性内存,如果在读取dex数据过程中超过了这块内存,就会出现dexopt失败。

由于dalvik加载系统模块时需要占用部分内存,facebook的推荐值我们自身dex的***值是4M,但我们发现在某些2.3的机器超过3.5M之后依然会dexopt失败。微信计算线性内存使用的是buck中asm-debug-all中提供的方法,计算class中会被dexopt中会读入线性内存的总大小:

(buck源码:https://github.com/facebook/buck)

通过baksmali反编译dex,也能得到相同的效果。在上面的那篇Facebook文章中,它们通过hack的方式更改系统线性内存的值,这涉及兼容性的问题,但按它们说只在三星的某些机型需要适配,so名称是:

(库地址:http://asm.ow2.org/)

责任编辑:chenqingxiang 来源: WeMobileDev
相关推荐

2017-03-02 15:09:29

AndroidAPK瘦身实践

2013-11-19 10:09:03

微信微信公号微信公众账号

2019-02-17 09:04:36

QQ微信马化腾

2016-04-01 10:34:29

APK压缩Android

2016-10-28 10:47:02

APP

2013-09-25 11:12:47

2015-02-13 10:08:19

微信

2015-10-10 16:02:36

React NativAndroid

2020-02-20 08:25:00

开源技术 软件

2020-07-27 15:06:14

微信张小龙焦虑

2012-03-01 10:43:38

Windows 8预览版

2013-08-08 10:13:25

微信

2013-11-29 10:56:28

2016-11-29 13:03:46

微信客户端跨平台组件

2015-07-16 10:11:38

TwitterHadoop集群优化

2019-04-12 08:28:18

物联网智慧城市IOT

2022-03-11 10:22:58

IT变革IT领导者数字化转型

2015-08-11 09:32:53

面试微信网易

2015-03-04 11:09:42

微信摇一摇红包

2013-05-21 10:06:01

开发者微信公众账号
点赞
收藏

51CTO技术栈公众号