dex 优化对 Arouter 查找路径的影响
一、前言
疑问:dex 文件是什么?dex 文件优化又是什么?
dex 文件优化会给项目带来什么问题,怎么解决这些问题?
1.1 APK 的编译和打包流程
1、通过 aapt 打包资源文件 res,对应生成 R.java、resources.arsc 和 res 文件(二进制 &非二进制保持原来的代码)
2、处理 aidl 文件,生成 java 接口文件(没有 aidl 则忽略)
3、通过 java compile 编译 R.java、 java 接口文件,生成对应.class 文件(java compiler)
4、运用 dex 命令,将.class 文件和第三方 sdk 库中的.class 文件转换成 classes.dex 文件
5、通过 apkbuilder 将 aapt 生成的 CompiledResources 和其他资源文件以及 classes.dex 文件打包生成 apk
6、同样的,可以使用 Jarsigner 工具,对上面的 apk 进行 debug 或者 release 签名
apk 的编译和打包流程图如下:

实际项目开发中,5、6 两个步骤,可以借助 jenkins 平台直接生成 release 包即可满足需求。
1.2 dex 文件的应用场景
再来看看 dex 文件常用的场景,比较流行的有:APK 的瘦身、热修复、插件化、应用加固、Android 逆向工程、64 K 方法数限制。
拿方法数限制举例,在上面的第 4 步,将 class 文件转换成 dex 文件,默认只会生成一个 dex 文件,单个 dex 文件中的方法数不能超过 65536,不然编译会报错,但是我们在开发 App 时肯定会集成一堆库,方法数一般都是超过 65536 的,解决这个问题的办法就是:一个 dex 装不下,用多个 dex 来装,gradle 增加一行配置:multiDexEnabled true。
dex 文件的应用场景网上介绍的很多,本文不做介绍。而是对项目中实际遇到的问题进行剖析,从而对 dex 优化有进一步的理解。
二、dex 到 vdex、odex
2.1 ART 预优化
ART(Android runtime)是什么,它是虚拟机,运营 java 代码、APP 用的。参考 Android 发展史,Android 5.0 把 ART 作为默认的虚拟机,而不是 Android4.4 及之前使用的 DVM(Dalvik VM)了。
回顾一下 DVM 和 ART 和 Android 的关系,我们先来了解运行 Java 的几种虚拟机的工作机制:(1)JVM:JVM 虚拟机运行的是 java 字节码。Java 文件到 JVM 的过程是:java -> java bytecode(class) -> java bytecode(jar)
DVM:DVM 虚拟机解析执行的 dex 字节码。Java 文件到 DVM 的过程是:java -> java bytecode(class) -> dalvik bytecode(dex)
ART:ART 虚拟机执行本地机器码。Java 文件到 ART 的过程是:java -> java bytecode(class) -> dalvik bytecode(dex) -> optimized android runtime machine code(oat)
可以看到,DVM 到 ART 的演变,实际上是 java 文件到虚拟机的执行代码的过渡,相对而言,ART 多了 oat 的过程,ART 使用 AOT(Ahead-Of-Time)编译,在应用第一次安装的时候,字节码预编译成机器码存在本地,DVM 是使用 JIT(Just-In-Time)编译,在应用每次运行的时候,字节码都需要通过编译器即时转换为机器码才能继续执行。ART 相对于 DVM,省去了每次解析字节码的过程,所以运行时占用的内存会减少,提升应用的运行效率。
2.2 ART 的运行方式
ART 在 Android5.0 时代,号称使用 AOT 即可让系统运行在 512M 的机器上。从 Android 7.0(简称 N)开始,ART 结合 AOT、即时 (JIT) 编译和配置文件引导型编译。
这几种模式可以组合配置,以谷歌的 Pixel 设备举例,配置了以下编译流程:
1)最初安装应用时不进行任何 AOT 编译。应用前几次运行时,系统会对其进行解译,并对经常执行的方法进行 JIT 编译。
2)当设备闲置和充电时,编译守护进程会运行,以便根据在应用前几次运行期间生成的配置文件对常用代码进行 AOT 编译。
下一次重新启动应用时将会使用配置文件引导型代码,并避免在运行时对已经编译过的方法进行 JIT 编译。在应用后续运行期间进行了 JIT 编译的方法将会被添加到配置文件中,然后编译守护进程将会对这些方法进行 AOT 编译。
ART 包括一个编译器(dex2oat 工具)和一个为启动 Zygote 而加载的运行时 (libart.so)。dex2oat 工具接受一个 APK 文件,并生成一个或多个编译文件,然后运行时将会加载这些文件。文件的个数、扩展名和名称会因版本而异,但在 Android O 版本中,将会生成以下文件:
vdex:其中包含 APK 的未压缩 DEX 代码,另外还有一些旨在加快验证速度的元数据。
odex:其中包含 APK 中经过 AOT 编译的方法代码。
art (optional):其中包含 APK 中列出的某些字符串和类的 ART 内部表示,用于加快应用启动速度。
2.3 vdex、odex 的作用
解压一个 APK(以厂商的系统应用包举例)的包,可以看到下面的结构,不含有任何 dex 文件

再看下这个应用在手机中的目录结构,vdex、odex 文件包含 apk 的所有代码,正常也会包含 classes.dex 文件。由于 vdex、odex 是机器码,没办法直接转成可以查看的二级制码查看(也可能是我使用的工具不对)。
2.4 vdex、odex 与 classes.dex 关系
可能是系统编译的 bug,也可能是生成了 ART 文件之后,对 odex、vdex 文件做了二次处理,现象是这样的,尝试获取 odex 中的 dex 文件,提示不含有 dex 文件。

为了再次确认 odex 里面是否真的含有 dex 文件,使用 010Editor 再次确认,可以看到 recent Files 下面仍然是没有 dex 文件的。

尝试获取 vdex 中的 dex 文件,也是无法获取的。

所以说,odex(或者 vdex)中含有 classes.dex 的说法是不正确的。
三、Arouter 是什么
阿里的一个路由组件,功能很多,我这边的实际使用场景是进行页面跳转。具体功能可以参考阿里峰会上对arouter的介绍。
借鉴峰会中提到的一点作为铺垫,也是我们下面将要讲述的一点。“最后想分享的就是 ARouter 的未来开发计划。未来 ARouter 会支持插件化并且支持生成映射关系文档,因为插件化是现在很多大型 APP 中会使用的技术方案,很多的 Dex 和功能是动态地下发到 APP 中的,而在这种情况下,是无法找到所有的 Dex 文件的,也就是对于没有加载过的 Dex 而言,里面的映射关系是跳转不过去的,所以一旦 Dex 文件位置发生变动,常规的方案是无法找到 Dex 的,也不能实现映射文件初始化,这一部分会在后面的版本中进行支持”。
阿里可以识别的 arouter 路径如下:

换句话说,arouter 可能因为 dex 文件的位置变化或者路径变化,而无法找到。
四、踩坑
4.1 现象
2.4 中提到了 odex 文件中不含有 dex,而 arouter 查找路径遵循分组按需加载的规则,归结到底,实际上就是对 class 文件的查找,如下图:

而 class 文件的信息记录在 dex 文件中,所以出现了异常,使用 arouter 进行页面跳转的时候,出现 classNotFound exception。
4.2 解决方案
想要找到解决方案,就要知道怎么样让 odex 对 arouter 路径不产生影响,这方面,可能在没有相关经验的时候,很难找到解决方案,只能一点点查找。通过搜索 ART 的工作原理,找到文章《配置ART》,其中文章提到:

也就是说通过配置 LOCAL_DEX_PREOPT 的属性,可以防止 odex 优化,于是找到Android.mk中设置该属性的地方进行设置 LOCAL_DEX_PREOPT := nostripping。

既在编译的时候做 dex 优化(生成 odex 文件),又不从 apk 里剥离 dex。于是有了下面的 apk 生成之后的路径对比,再看下 dex 不被剥离的路径,下面含有了 classes.dex 文件。

使用 jadx 打开这个 classes.dex 文件,发现 arouter 的路径文件就在这里,所以 arouter 的跳转正常了,异常不再出现。

五、总结
odex 优化这种系统做的事情,往往会出现一些意想不到的结果,如果你负责厂商的应用,经常需要内置项目,这时候要注意了,当你的应用中含有第三方框架的时候,要注意路径、资源的引用都是没问题的,虽然正常情况下,odex 文件不会对你的路径产生干扰,但是也难免 odex 出现失误,因为对于 odex 来说,里面的资源无需保存,生成 art 文件能够运行即可。合理使用 art 的配置,可以帮助解决很多问题。
作者:vivo 互联网客户端团队-Xu jie
版权声明: 本文为 InfoQ 作者【vivo互联网技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/eec8cc37423242f3b34072015】。文章转载请联系作者。
评论