ReDex开源:让Android app更小更快

去年秋,  我们发布了ReDex,一个减小安卓app大小以提高性能的工具。那时候我们致力于诸如压缩,内嵌以及清除僵尸代码这样的优化来减小字节码,但是我们并没有把它们放到产品中测试。现在我们已经这样做了。11月的时候,我们发布了第一个ReDex优化版的Facebook安卓客户端,比原来小了25%,同时启动速度提高了30%。今天我们很高兴宣布将ReDex开源,希望开发者能利用这些工具让每一个安卓app都更小更快 -不仅仅是Facebook。

blob.png

新的优化点

在以前的文章中,我们讨论了几个ReDex中的优化点。从那之后,我们又实现了几个新的优化,它们帮助我们的产品获得了速度提升。

基于反馈的class布局

一种加速方法就是优化app在磁盘上的字节码结构。默认情况下,dex文件中的class并不是基于它们运行时的行为来组织的,而是基于编译工具链碰巧使用的顺序。当app启动的时候,磁盘必须 找出随机分布在很大文件中的class数据,这会导致延迟,尤其在内部闪存速度很慢的旧设备上。我们认为class的位置可以用来提高字节码的加载性能。

我们使用在本地代码优化中很常见的基于反馈的优化技术(FDO)。为了完成FDO,我们首先在实验室收集运行时数据。因为冷启动性能对用户来说很重要,我们在测试设备上跟踪冷启动期间加载的class,然后把这些class装入

ReDex,它把冷启动期间用到的class放在最前面。这种布置最小化了在启动时从闪存获取字节码的次数。

移除接口

基于接口而不是实现的编程是一种很好的软件开发实践。但是,有时候一个发行版的app中存在一些只有一个实现的接口。这个附加的接口占用了额外的内存并占用了额外的方法引用。另外,在运行时调用接口方法要比调用虚拟方法低效。

ReDex可以移除不必要的接口。为了移除这些接口,我们先执行一次检查,遍历类的结构并找出那些只有一个实现的接口。一旦找到一个只有一个实现的接口,我们就跟踪代码并重写调用方法,让它直接触发实现方法,然后删除此时已经无用的接口。

移除 metadata

Dex文件包含一些在运行时无用的metadata。比如,每个类都都包含了定义它的java文件的名称。我们写了一个简单的通行名单,找出这些源文件的引用并把它们替换成dex中已经出现过的其它字符串。因为文件名一般都很长,所以这种优化可以节省很可观的存储空间(这个地方译者也不是很明白)。

另一个要考虑的metadata就是注解。虽然有些注解是运行时的,但是多数只被分析工具或者编译系统使用。我们添加了一个白名单,把无用的注解从发行版本中删除。

性能数据

我们在实验室中的一套设备上做了性能测试,同样收集了来自真实世界的设备的远程数据。我们实验室的测量数据很感人:在一个运行Android 4.4系统的纯净(大概就是没装多少东西的意思吧)的Nexus 4上,我们发现启动时间大概减少了2到1.6秒 - 速度提升了20%。

在我们的所有优化都稳定了之后,我们在去年11月发布了ReDex的第一个版本。在这个版本的帮助下,我们把Facebook的dex减小了大约25%。这节省了不小的磁盘空间,而且因为系统需要获得的字节码更少,冷启动速度也会快一些。

真实环境中的结论也差不多。在使用Facebook的人群中,总体来说我们看到大概减少了20%的冷启动时间。在高端手机获得速度提升的同时,我们欣慰的发现在普通手机上ReDex提供了最大的帮助 - 高达25%到 30%的速度提升。而普通手机是世界范围内使用Facebook的主流人群。出现这种情况的主要原因可能是内存压力:内存小的设备更能从字节码的减小中获得好处。

为开发者而设计

对我们的团队来说,打造一个既能能提高安卓版facebook用户体验,又能方便开发者使用的工具是很重要的。我们从一个开发者的角度去设计ReDex:

  1. 必须得快。具有快速的编译过程对开发者来说是非常重要的。编译太慢不仅仅需要等待很长的编译时间,而且它还减慢了开发新的优化,同时还让调整设置变的困难。我们努力优化ReDex本身:即使在Facebook这种体量的app上,它也只需在30秒之内运行。

  2. 必须具有简单的接口。dex字节码只是安卓apk格式的一部分。我们的ReDex处理了从apk解包字节码的所有细节,分析,重新打包成相同的格式。这种设计使得把ReDex添加到现有编译工具链变的非常简单,不需要添加支持脚本。

  3. 必须可配置。为了让app更小,ReDex将清除它认为可以优化掉的代码。但是有几种情况ReDex无法判断代码是否还有用。一种是JNI:一个使用了本地代码的app可以在本地代码中实例化对象与调用方法,而这是字节码优化器不能看到的。另一个就是反射:一个程序可以使用随机的字符串去invoke方法,因此ReDex确定那些方法无用。其实,很多事情都无法通过JNI或者反射得到,所以我们就假设它是不能优化的。但是为了以防万一,你可以轻易的指定哪些方法不被优化。只需为ReDex提供一个JSON配置文件,让它知道什么东西不用管。我们的在线文档描述了如何去写好一个配置文件。

开源

能把这个技术贡献给整个安卓开发社区,我们真的感到非常高兴。希望能帮助其它的app变的更快。我们把解析和产生dex的library与优化分离开来。我做这个是为了创建一个平台来减少重复编写dex优化工具的工作。现在你可以在GitHub 上找到ReDex,可以自己尝试一下。我们欢迎各种反馈:非常高兴能收到建议和issues,更欢迎pull requests。

英文原文:https://code.facebook.com/posts/998080480282805/open-sourcing-redex-making-android-apps-smaller-and-faster/