本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583b9e3ee8992c2c2df6e6ac

导语

早在去年10月份,facebook就发布了介绍redex的文章,这个据说可以直接对apk做处理,既提高启动性能,又可减少安装包的利器让安卓开发者们都心动不已。直到今年4月,redex终于开源了,我们也第一时间对redex做了研究(有观众可能要说我骗人,这都11月了怎么还第一时间呢?好把这个总结是拖了很久才写),虽然由于坑多,最终没有接入到项目构建中,但受Interdex启发,在应用冷启动速度优化方面有了新的收获。

PS:本篇提到的冷启动速度优化,不包括Android 5.0及以上系统

一、redex的使用与坑

1.安装与使用

使用redex的第一个坑就是环境。很遗憾的是这个工具不支持windows系统(用mac开发的壕请忽略),只好装虚拟机来跑ubuntu。解决了系统,就可以按照github上的官方指引一步步来了,这里需要安装茫茫多的依赖库和解决若干环境问题,幸好各种典型issue已经有了解决方案,这里不再赘述。

2.优化原理与配置

Redex的优化项众多,并且可以很方便的修改配置文件来选择需要执行的优化,默认的配置文件如下

根据官方的介绍文档,redex的优化主要有以下几项:

A.内联。

简单说就是去除一些多级调用的中间层级,举个例子:

func1 -> static func2 -> static func3

优化后就是

func1 -> static func3

这样可以减少函数调用时间和字节码。除了静态方法调用,对象引用也有类似优化。

B.删除无用代码,移除空类。

C.对于只有一个实现类的接口或父类,直接用实现类代替。

D.SynthPass

翻译不能,官方例子,内部类B访问外部类A的private static变量,compile后其实是通过生成额外的acces方法来帮助内部类访问外部类私有成员。这个优化可以去除额外生成的字节码,方法相当于把变量的作用域改成public。

E.字符串缩减,包括提供字节码层面的混淆能力,类似Proguard,以及DEX文件中metadata的优化,可以有效缩减安装包大小。

F.Interdex

需要使用者提供程序启动时加载类序列作为配置文件,按此顺序调整dex中类的顺序,可以有效提升冷启动速度,提升幅度在30%左右。优化的原理facebook推测是优化读取IO和内存(按研究的结果来看其实另有原因,后面再说)

3.实践中遇到的坑

如此多的优化项累加,想来效果应该非常可观。但残酷的现实是,经过对手Q安装包的处理验证,redex中还存在不少bug和坑,接入使用的性价比不高:

A.IlegalAccessError

这是redex的一个bug,原因是在内联优化中,移除中间层的方法时没有考虑作用域,比如:

Func1 -> public static func2 -> private static func3

会被优化成:

Func1 -> private static func3

而调用类又不能访问其他类的私有方法,导致抛异常(这个问题有不少issue,近期redex似乎已经修复了,还未验证)。

B.NoClassDefError

一个比较诡异的问题,运行时报这个错,但反编译Dex文件,这个类是存在的,怀疑是redex的bug,github也有少部分类似的issue,原因未明。

C.NoSuchMethodError

一个坑。因为手Q里很多业务是以插件机制运行的,部分插件是非独立的,也就是和手Q工程一起编译,并且会引用手Q代码,在编译完成后,这些插件也分别打包好存放在手q的apk里。这样会导致的问题是:

redex在做优化时可能会把手Q部分方法移除,如果插件刚好引用了这个方法,就出现NoSuchMethodError了。

D.Interdex

这个优化项会完全打乱原有的dex分布,甚至dex的数量也会发生改变,用来校验分dex是否注入成功的Foo类,以及补丁patch也被打乱,对启动时分dex注入,补丁等逻辑都有很大影响。

E.签名

redex执行后需要对apk重新签名,而手Q在签名之后还有一些优化逻辑。

这个时候redex可配置优化项的方便之处就体现出来了。遇到问题时,可以把可疑的优化项屏蔽掉,继续验证。可即使如此,屏蔽到最后悲催的发现可用优化项已经不多,优化的效果也不太明显(安装包可以减少100k左右,启动速度方面因为interdex需要较大改动,未尝试)。仅存的几个优化项没经过更细致的测试也可能存在隐患,而就算只使用这少数优化,在编译脚本修改和rdm构建环境搭建上也会有很大的工作量。

二、Interdex,冷启动速度优化

想直接接入redex成本较大,但要我们直接放弃这些优化空间,内心也是拒绝的。那么我们能否参考facebook的思路,尝试自行实现一些优化项呢?

在redex中,大部分优化原理都需要解析dex格式,从中还原出引用、继承关系,加以分析,工作量巨大。但Interdex比较例外,这个优化不需要去分析类引用,它只需要调整Dex中类的顺序,把启动时需要加载的类按顺序放到主dex里,这个工作我们完全可以在编译过程中实现,而且这个优化可以提升启动速度,优化效果从facebook公布的数据来看也比较可观,性价比高。

1.如何实现Interdex

根据interdex官方介绍的原理,我们可以知道要实现这个优化需要解决三个问题:如何获取启动时加载类的序列?如何把需要的类放到主dex中?如何调整主dex中类的顺序?

A.如何获取启动时加载类的序列?

redex中的方案是dump出程序启动时的hprof文件,再从中分析出加载的类,比较麻烦。这里我们采用的方案是hook住ClassLoader.findClass方法,在系统加载类时日志打印出类名,这样分析日志就可以得到启动时加载的类序列了。

B.如何把需要的类放到主dex中?

redex的做法应该是解析出所有dex中的类,再按配置的加载类序列,从主dex开始重新生成各个dex,所以会打乱原有的dex分布。而在手q中,分dex规则是编译脚本中维护的,因此我们可以修改分包逻辑,将需要的类放到主dex。

C.如何调整主dex中类的顺序?

开源就是好。Android编译时把.class转换成.dex是依靠dx.bat,这个工具实际执行的是sdk中的dx.jar。我们可以修改dx的源码,替换这个jar包,就可以执行自定义的dx逻辑了。简单说下具体修改方法:

这里需要对dex的文件格式做一定了解,不再细说,网上有一篇很好的文章,有兴趣可以了解下

Android逆向之旅---解析编译之后的Dex文件格式

借网上的一张图,dex文件的基本结构如下:

从dex的文件格式我们可以知道,dex被据划分为多个section,一个类的完整信息也被分散到各个section里。想从dex中解析一个类必须要先从classDef段找到类定义,从中找到类包含的各种信息的偏移地址,再从对应地址去读取数据,所以要调整dex的类排列顺序,理论上只需要对classDef段修改即可。

(从这里看其实类的排列顺序对读取时的内存影响应该不大,因为在dex中类的数据并不是连续存储的)

在dx执行时,最终将dex数据写入到文件也是以section为单位逐个写入,并且每个section写入前都会执行orderItems做排序,修改这个方法即可实现我们的目的。

2.优化效果

一番折腾后,终于实现将启动时加载的类按顺序放到主dex中了,赶快用专项测试跑下数据,启动过程actLoginA的耗时减少了30%左右,提升效果还是比较明显的,数值上与facebook的结论也比较接近。

可惜没能高兴太久,当我把改动上传到rdm,用rdm构建的release包做专项测试时,发现并没有什么效果。此时内心是有点懵x的,难道是专项测试时偶现了误差?还是测试时用的参照包和我本地包不是一个version?

还是我眼花看错了,实际没效果?

怎么办,前一天写日报好像已经把优化30%的结果同步出去了,过了一天还能撤回邮件吗?

冷静,这个时候不能着急,总之先冷静下来找找哪里有时光机。

经过反复、仔细的验证,可以确认的事实是,rdm构建的release包无明显优化,本地debug包和rdm构建的debug包,都有明显优化。

3.为啥release不生效?

手q最终发布的包必然是release包,只对debug包生效的优化并没有什么作用。并且这个优化的原理我们也没有弄清楚,facebook的理论主要是优化IO和内存带来的速度提升,但前面也提过,从dex文件的结构来看,这个解释并不能让人信服。所以还要继续分析,如果弄明白了为什么release包不生效,也许就可以推测出优化原理。

首先怀疑的是混淆。Release构建中会做混淆,很多类名都会变化,而我们优化时用的类加载序列是原始类名,所以在release构建时不能正确的调整顺序。嘿嘿,应该是原因了把,这个好修复,混淆是在dx之前执行的,只要混淆后拿到混淆表,把类加载序列里的类名替换成混淆后的即可。修改后再次测试,结果仍没什么变化。

再找原因,release构建有做ZipAlign优化而debug没有,是不是这个影响?验证后排除。

继续怀疑,是不是release包类加载顺序变了?这个按说是不太可能,但抱着死马当活马医的心态试了下,果不其然是匹死马,排除。

finally,在和hyim、大龙两位老司机讨论时发现了新的嫌疑人,插桩。当时手q使用的热补丁是classloader方案:反射修改classloader的DexPathList。这个方案为了解决加载补丁类时verify出错的问题,需要对所有的类进行插桩,而插桩逻辑只有在release构建才会执行。在relesse构建中去掉插桩逻辑,再次测试,actLoginA终于有了提升。

4.优化原理

插桩的目的是避免安装时虚拟机做pre-verify,让类打上CLASS_ISPREVERIFIED标识。这会导致Interdex优化失效,而系统做pre-verify是为了提升性能,再结合Interdex的实现,综合来看interdex真正的优化原理就比较明显了:

将启动时加载的类放到主dex,提升了这些类的内聚,让更多的类满足pre-verify的条件,在安装时就做了校验和优化,以减少首次加载的耗时,从而优化冷启动耗时。

(这个结论也再次证明dex中类排列顺序应该不影响性能,因为打不打pre-verify只看类引用关系。去掉启动类排序逻辑后再次验证,确实仍有明显优化效果)

而插桩会导致所有类必然不能打上pre-verify,所以不管怎么调整类分布,都没用。

一个小疑问:手Q刚开始用热补丁时,为啥没有发现明显的actLoginA下降?

原因:手q有多个分dex,并且之前主要是按包名来做分dex,所以主dex中除了主依赖集外,剩余的很多类可能都已经不满足pre-verify条件了,所以插不插桩区别不大。

三、总结

  1. Interdex优化确实可以明显提升应用冷启动速度,原理也比较简单:把互相引用的类尽量放在同个dex,增加类的pre-verify。这个思路其实不仅仅可以用在启动上,一些其他的关键场景也可能用类似方法提升性能。不过这个优化与修改classloader.DexPathList的热补丁方案有冲突,想要二者兼得需要选择其他补丁方案。

比如zhekai的新方案详见

QFix探索之路——手Q热补丁轻量级方案

  1. redex还是一个很好的工具,有很多优化项可以挖掘,小型app相对来说应该更容易接入,大型项目会遇到更多的坑,直接接入不易,但也可以从中了解到新的思路。赞开源精神。

  2. 保持怀疑和好奇。再牛x的项目,也不能所有理论都是对的,还是要多实践。比如Interdex中调整类顺序,在这个优化项本身是没什么用,而整个研究中这部分是最花费时间的。

    (当然长远来看,了解dx执行和自定义dx实现,了解dex文件结构都是挺有用的,这波不亏)


更多精彩内容欢迎关注bugly的微信公众账号:

腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!

【腾讯Bugly干货分享】Redex初探与Interdex:Andorid冷启动优化的更多相关文章

  1. 【腾讯Bugly干货分享】微信iOS SQLite源码优化实践

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57b58022433221be01499480 作者:张三华 前言 随着微信iO ...

  2. 【腾讯Bugly干货分享】深入源码探索 ReactNative 通信机制

    Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 本文从源码角度剖析 RNA 中 J ...

  3. 【腾讯Bugly干货分享】Android内存优化总结&实践

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/2MsEAR9pQfMr1Sfs7cPdWQ 导语 智 ...

  4. 【腾讯Bugly干货分享】Android性能优化典范——第6季

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/580d91208d80e49771f0a07c 导语 这里是Android性能优 ...

  5. 【腾讯Bugly干货分享】基于RxJava的一种MVP实现

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57bfef673c1174283d60bac0 Dev Club 是一个交流移动 ...

  6. 【腾讯Bugly干货分享】微信热补丁Tinker的实践演进之路

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ad7a70eaed47bb2699e68e Dev Club 是一个交流移动 ...

  7. 【腾讯Bugly干货分享】H5 视频直播那些事

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57a42ee6503dfcb22007ede8 Dev Club 是一个交流移动 ...

  8. 【腾讯Bugly干货分享】JSPatch 成长之路

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/579efa7083355a9a57a1ac5b Dev Club 是一个交流移动 ...

  9. 【腾讯Bugly干货分享】React Native项目实战总结

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/577e16a7640ad7b4682c64a7 “8小时内拼工作,8小时外拼成长 ...

随机推荐

  1. 解决浏览器Adobe Flash Player不是最新版本问题

    关键:选择谷歌浏览器的PPAPI版本的flash下载直接安装即可 搜索: Adobe Flash Player PPAPI 下载地址: http://www.wmzhe.com/soft-30259. ...

  2. XmlValidationHelper XSD、Schema(XmlSchemaSet)、XmlReader(XmlValidationSettings)、XmlDocument、XDocument Validate

    namespace Test { using Microshaoft; using System; using System.Xml; using System.Xml.Linq; class Pro ...

  3. HDU 1848 SG函数博弈

    Fibonacci again and again Problem Description   任何一个大学生对菲波那契数列(Fibonacci numbers)应该都不会陌生,它是这样定义的:F(1 ...

  4. 多栏多列布局(display:flex)

    display:flex 多栏多列布局浏览器支持情况:火狐直接支持w3c无前缀写法,谷歌和opera支持-webkit- 前缀写法,比较适合移动端开发使用, display:flex 这个牛逼的css ...

  5. Daily Scrum Meeting ——SecondDay(Beta)12.10

    一.Daily Scrum Meeting照片 二.Burndown Chart 三.项目进展(check-in) 1. 修复两个Alpha版本所遗留的BUG 2. 着手修改参与者与发布者的侧滑框,改 ...

  6. Theano 学习笔记(一)

    Theano 学习笔记(一) theano 为什么要定义共享变量? 定义共享变量的原因在于GPU的使用,如果不定义共享的话,那么当GPU调用这些变量时,遇到一次就要调用一次,这样就会花费大量时间在数据 ...

  7. shell中${ } 的一些特异功能

    假设我们定义了一个变量为: file=/dir1/dir2/dir3/my.file.txt 我们可以用 ${ } 分别替换获得不同的值: ${file#*/}:拿掉第一条 / 及其左边的字符串:di ...

  8. [转] 前后端分离开发模式的 mock 平台预研

    引入 mock(模拟): 是在项目测试中,对项目外部或不容易获取的对象/接口,用一个虚拟的对象/接口来模拟,以便测试. 背景 前后端分离 前后端仅仅通过异步接口(AJAX/JSONP)来编程 前后端都 ...

  9. iMetro

    body { background:#FFFFFF url("http://images.cnblogs.com/cnblogs_com/mookmark/745172/o_8.jpg&qu ...

  10. 苹果手机微信上form表单提交的问题

    场景:前端页面请求后端php,返回带form表单dom元素,然后将其追在页面上,返回的html字段中包含表单自动提交的代码,想法是将带有表单自动提交的dom元素追加到页面上,然后表单自动提交到另外一个 ...