ODEX简介
什么是ODEX?
如果你的apk文件或者jar包没有做过odex优化的话,打开你的文件,你一定会找到一个名字叫做classes.dex的文件(通常情况下,我们看到的实际上是一个以.apk为后缀名的压缩文件,classes.dex文件包含在压缩文件中),这个就是我们的java源码经过ADT编译之后转换成的可执行文件。
对于使用Dalvik虚拟机的Android版本,当系统第一次启动时,会对.apk/.jar文件里的classes.dex文件进行一次“翻译”,“翻译”的过程也就是守护进程installd的函数dexopt来对dex字节码进行优化,实际上也就是将它转化为.odex文件,最终odex文件被保存在手机的VM缓存目录/data/dalvik-cache下(注意这里.odex文件名字仍是.dex后缀,格式如:system@priv-app@Settings@Settings.apk@classes.dex)。
对于使用ART虚拟机的Android版本,首次进系统时,调用/system/bin/dexopt工具来将.apk/.jar里的classes.dex文件翻译成本地机器码,同样保存在/data/dalvik-cache下。
无论是dalvik里对classes.dex进行优化,还是art里对classes.dex进行翻译成本地机器码,最终得到的结果都保存在相同名称的odex文件里,但是前者实际是一个dey文件(表示优化过的dex文件),而后者是一个oat文件(实际上是自定义的elf文件,里面全是机器码)。简单来说,无论是DVM,还是ART,优化的结果都是一个odex文件,只是这两种odex文件有着本质区别(一个是dey字节码,一个是oat机器码)。之所以这么设计,主要通过这种方式,原来任何通过绝对路径引用了该odex文件的代码就都不需要修改了,可以理解为这是ART与Dalvik兼容的结果。所以首次开机时,启动比较慢,就是因为系统在做这些事情,预置的APK越多,启动时间就越长。
如果将这件事情,在编译系统时就做好,装到image里去,那么首次开机的时间,就会节约很多,不过这样做也会让生成的image变得比以前大。
什么是dalvik-cache?
当Android启动时,DVM监控所有程序(APK文件)和框架,并且为他们创建一个依存关系树。DVM通过这个依存关系树来为每个程序优化代码并存储在Dalvik缓存中。这样,所有程序在运行时都会使用优化过的代码。这就是当你刷一个新ROM时,有时候第一次启动时间非常长的原因。当一个程序(或框架库)发生变更,DVM将会重新优化代码并且再次将其保存在缓存中。
/cache/dalvik-cache存放的是system上的程序生成的dex文件,/data/dalvik-cache存放的是/data/app生成的dex文件。
ODEX优化有什么用?
ODEX的用途是分离程序资源和可执行文件、以及做预编译处理,达到加快软件加载速度和开机速度的目的。一般来说,厂商的原厂系统都会为自己的ROM做ODEX优化处理以提高性能。
很多人都会疑问,Android5.0开始,默认已经使用ART虚拟机,弃用Dalvik了。应用程序会在安装时被编译成OAT文件(ART上运行的格式),ODEX还有什么用呢?这里我们先看下Google的回答:
Dex file compilation uses a tool called dex2oat and takes more time than dexopt.
The increase in time varies, but 2-3x increases in compile time are not unusual.
For example, apps that typically take a second to install using dexopt might take 2-3 seconds.
DEX转换成OAT的这个过程是5.0以上系统用户在安装程序或是刷入ROM、增量更新后首次启动时必然执行的。按照Google的说法,相比做过ODEX优化,没有做过优化的DEX转换成OAT需要花费更长的事件,一般是2-3倍。比如安装一个ODEX优化过的程序假设需要1秒钟,未做过优化的程序就需要2~3秒。
由此可见,虽然Dalvik被弃用了,但ODEX优化在Android5.0系统以上依旧起着作用。
编译时ODEX 相关变量
这里简单介绍一下Android编译时,odex优化相关的一些编译选项,可以针对自己的的需要,设置自己系统的这些编译选项,以达到不同的目的。
-
WITH_DEXPREOPT
这个变量相当于编译时odex优化的总开关,只要想在编译时生成odex文件,就必须设置为true。开启该参数后,会对APK、JAR以及内核镜像进行优化。其中,针对APK、JAR的最直观的优化体现就是,程序的dex被转换成odex。
不过这里需要注意的是打开WITH_DEXPREOPT 宏之后,预编译时提取Odex会增加一定的空间,预置太多apk,会导致system.img 过大,而编译不过。遇到这种情况可以通过删除apk中的dex文件、调大system.img的大小限制,或在预编译时跳过一些apk的odex提取。
-
WITH_DEXPREOPT_BOOT_IMG_ONLY
在WITH_DEXPREOPT:=true的情况下,若同时设置WITH_DEXPREOPT_BOOT_IMG_ONLY为true,则编译时只对PRODUCT_BOOT_JARS变量里定义的jar包进行odex优化,生成boot.oat和boot.art。
-
DONT_DEXPREOPT_PREBUILTS
打开这个变量后,编译时不会对Android.mk中包含了include$(BUILD_PREBUILT)的Apk进行odex优化。对于一些以后需要自升级的apk这样做,可以节省空间。
-
LOCAL_DEX_PREOPT
可在各个Android.mk文件里单独设置这个变量,它只会影响当前Android.mk编译的单个jar包或者apk文件。当设置为true时,classes.dex文件将从.apk/.jar文件中删除;设置成
nostripping,classes.dex文件仍保留在.apk/.jar文件里。
-
DEX_PREOPT_DEFAULT
当WITH_DEXPREOPT:=true时,LOCAL_DEX_PREOPT如果没有进行单独设置,那么它的值就是DEX_PREOPT_DEFAULT,所以可以通过设置DEX_PREOPT_DEFAULT的值,统一改变所有的LOCAL_DEX_PREOPT的默认值。
一个常见的场景是,DEX_PREOPT_DEFAULT:=nostripping,导致最后生成的image里同时存在classes.dex和.odex文件,造成空间浪费。
ODEX优化提高首次开机速度
现在Android手机都需要预装很多应用,这些应用的APK主要在/system/app,/system/priv-app/,/system/vendor/app等目录下。如果没有做ODEX优化,在首次开机时,SystemService.java会调用PackageManagerService对这几个目录下的APK做dexopt的优化,生成oat文件。APK越多,首次开机的事件就会越长。首次开机时,通常在手机上看到“正在优化第*个应用,总共*个应用”,这就是在对APK做dexopt的优化。
如果我们要提高首次开机的速度,可以做如下设置:
1、 在工程代码/device/qcom/项目名/BoardConfig.mk修改两个设置
修改下面两个设置,在编译时,对jar、APK都做ODEX优化,生成对应的ODEX文件:
DISABLE_DEXPREOPT := false
WITH_DEXPREOPT := true
如果不想在编译时做ODEX优化,可以注释掉这两行,或者把这两个值设置成:
注释:
#DISABLE_DEXPREOPT := false
#WITH_DEXPREOPT := true
或:
DISABLE_DEXPREOPT := true
WITH_DEXPREOPT := false
2、 如果设置了:
DISABLE_DEXPREOPT := false
WITH_DEXPREOPT := true
在编译的时候,/system/framework/目录下面的jar包,和/system/app,/system/priv-app/,/system/vendor/app等目录下的apk文件,都会在编译时做ODEX优化。
如果不想jar包做ODEX优化,可以在/build/core/java_library.mk文件中设置:
LOCAL_DEX_PREOPT := false
这样在编译时,jar包就不会做ODEX优化。
3、 在实际开发中,有些apk如果做了ODEX优化,可能会出问题,可以通过在apk的编译目录Android.mk文件中添加:
LOCAL_DEX_PREOPT := false
这样该apk就不会做ODEX优化。
如果jar包和apk都做ODEX优化,在项目中,原来首次开机速度3分钟左右可以提高到1分种左右。
源码解惑
PackageManagerService初始化
在PackageManagerService初始化过程中,安装应用的时,PKMS都会通过另一个类Installer的方法dexopt()来对APK里面的dex字节码进行优化:
try {