共计 3595 个字符,预计需要花费 9 分钟才能阅读完成。
一、前言
疑难: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