对于如何提高安卓6.0首次开机启动的时间,其关键就是打开系统的预编译odex优化开关。简而言之,就是将系统的jar包提前在服务器上根据相对应的平台进行预优化,对应的预装应用也从apk的class文件中优化得到odex文件从而加快首次开机PMS便利扫描apk包的时间。
odex 预编译加快开机速度
各种虚拟机的了解
JVM虚拟机运行的是java字节码:
java -> java bytecode(class)-> java bytecode(jar)
注!java虚拟机基于栈,基于栈的机器必须使用指令来载入和操作栈上的数据,所需指令相对来说比较多。Dalvik虚拟机解释执行的dex字节码:
java -> java bytecode(class)-> dalvik bytecode(dex)
注:相对JVM,Dalvik基于寄存器,且经过优化并允许有限的内存中同时运行多个虚拟机实例,每个Dalvik应用作为一个独立的Linxu进程执行。如果一个应用中有很多类,编译后会相应生成很多class文件,class文件之间也会有不少冗余信息,dex格式文件把所有classs文件内容整合到一个文件,这样可以减少整体文件占用,IO操作,同时也提高了类的查找速度。此外,dex格式文件增加了新的操作码支持,文件结构也相对简洁,使用等长的指令来提高解析速度。而且dex文件会尽量扩大只读结构的大小,来提高进程间数据共享的速度。ART虚拟机执行的本地机器码:
java -> java bytecode(class)-> dalvik bytecode(dex)-> optimized android runtime machine code(oat)
注:ART所使用的AOT(Ahead-Of-Time)编译,在应用首次安装时,字节码预编译成机器码存储在本地,也就是说在程序运行前编译。而Dalvik是典型的JIT(Just_In_Time),此模式下,应用每次运行的时候,字节码都需要即时编译器转换为机器码再执行,也就是在程序运行时编译。因此在App运行时,ART模式相对于Dalvik省去了解释字节码的过程,占用内存也相应减少,进而提高App的运行效率。
Odex相关介绍
apk内容
在编译打包APK时,Java类会被编译成一个或者多个字节码文件(.class),通过dx工具CLASS文件转换成一个DEX(Dalvik Executable)文件。通常情况下,我们看到的Android应用程序实际上是一个以.apk为后缀名的压缩文件。我们可以通过压缩工具对apk进行解压,解压出来的内容中有一个名为classes.dex的文件。那么我们首次开机的时候系统需要将其从apk中解压出来保存在data/app目录中。
Dalvik虚拟机:
如果当前运行在Dalvik虚拟机下,Dalvik会对classes.dex进行一次“翻译”,“翻译”的过程也就是守护进程installd的函数dexopt来对dex字节码进行优化,实际上也就是由dex文件生成odex文件,最终odex文件被保存在手机的VM缓存目录data/dalvik-cache下(注意!这里所生成的odex文件依旧是以dex为后缀名,格式如:system@priv-app@Settings@Settings.apk@classes.dex)。
Art虚拟机:
如果当前运行于Art模式下,Art同样会在首次进入系统的时候调用/system/bin/dexopt工具来将dex字节码翻译成本地机器码(而非Dalvik虚拟机仅仅优化的字节码,机器码不需要解释器解释便可直接运行),保存在data/dalvik-cache下。
相关注意
从上面虚拟机运行机制可以看出,无论是对dex字节码进行优化,还是将dex字节码翻译成本地机器码,最终得到的结果都是保存在相同名称的一个odex文件里面的。但是前者Dalvik对应的是一个dey文件(表示这是一个优化过的dex),后者Art虚拟机对应的是一个oat文件(实际上是一个自定义的elf文件,里面包含的都是本地机器指令)。简单来说无论是Art模式,还是DVM,优化的结果都是一个odex文件,只是这两种odex文件有着本质的区别(一个是dey字节码,一个是oat机器码)。之所以这么设计,主要通过这种方式,原来任何通过绝对路径引用了该odex文件的代码就都不需要修改了,可以理解为这是art与dalvik兼容的结果。
优化缘由
由于在系统首次启动时会对应用进行安装,那么在预置APK比较多的情况下,将会大大增加系统首次启动的时间。从前面的描述可知,既然无论是DVM还是ART,对DEX的优化结果都是保存在一个相同名称的odex文件,那么如果我们把这两个过程在ROM编译的时候预处理提取Odex文件将会大大优化系统首次启动的时间。
预编译提取Odex
几个重要的宏定义
安卓源码中提供了宏定义开关提前odex优化代码,对应于各个分支的BoardConfig.mk中定义。分别是DISABLE_DEXPREOPT、WITH_DEXPREOPT这两个宏。但这两者还是有点分别的。
DISABLE_DEXPREOPT
在user-builds中,默认是开启的,即进行odex优化操作,如果不需要优化可以注释掉或者强制设置为true。
WITH_DEXPREOPT
在eng/others-build中,默认是关闭的,不进行优化。其他user-builds版本上是默认开启的,意思就是user-builds要开odex预编译。这会导致system image中的所有东西都被提前优化(pre-optimized),导致system image非常大。
LOCAL_DEX_PREOPT
在App的Android.mk文件里面添加这个宏开关,可以控制这个App时候要预编译。一般预置的第三方App都会把这个宏开关置为false,这样既可以避免提取odex出现异常导致App功能异常,也能节省一定空间消耗。
odex预提取
由于编译时提取Odex会增加一定的空间,预置太多apk,会导致system.img 过大,而编译不过。可以适当增加此分区大小,一般在 BoardConfig.mk 中有配置,直接调整,注意存储空间有限,这里增加必然要相应减少其他分区的大小,以避免编译后刷写系统失败。
userdata (1G) BOARD_USERDATAIMAGE_PARTITION_SIZE := 0x40000000 这里的 0x40000000 是十六进制的转换,1G = 1024*1024*1024,之后再转换成16进制的值
开启 BoardConfig.mk 中 关键的两个宏开关。DISABLE_DEXPREOPT := true 和 WITH_DEXPREOPT := true。这里建议是打开和关闭注释的方式,以避免默认配置导致自己理解混乱。
DISABLE_DEXPREOPT := true WITH_DEXPREOPT := true
预编译开关控制的不仅仅是apk,包括/system/framework/目录下面的jar包也会参与预编译优化,如果想控制不参与jar的odex优化,可以在 /buid/core/java_library.mk 文件中设置,这样在编译时,jar包就不会做odex优化。
LOCAL_DEX_PREOPT := false
实际编译中,因为各种未知原因导致编译出错,或者编译成功刷写开机后系统应用莫名crash,这里提供两种方法跳过对应应用,避免其进行预编译优化操作导致系统莫名问题。
方法一:
单独控制。直接在对应源码apk的Android.mk文件中添加如下脚本,确保其不进行odex优化操作。 LOCAL_DEX_PREOPT := false
方法二:
全局控制,在目录 \build\core\dex_preopt_odex_install.mk 中对应位置添加过滤脚本代码: # add by allies begin # 添加过滤应用,否则导致odex优化编译不过,应用crash导致系统崩溃 # ifeq ($(LOCAL_MODULE),MLeanbackTv) ifneq (,$(filter $(LOCAL_MODULE),MLeanbackTv MTvService)) LOCAL_DEX_PREOPT:= endif # add by allies end # 在此处原生代码上面添加 built_odex := installed_odex :=
MLeanbackTv 可替换为需要跳过提取odex的apk的 LOCAL_MODULE 。如果需要过滤多个,可以操考实例中的代码,使用filter过滤,逗号后面直接列出过滤的应用,中间以空格隔断即可。
总结
安卓更新迭代到5.0后,其虚拟机由Dalvik转为Art具有划时代的意义。安卓设备的卡顿的现象出现了明显的转变,这是安卓进步的重要一步。对于系统进行odex与编译优化,虽然极大程度上提高了首次开机的时间,但并非完全有益,主要总结有以下几点:
- odex优化进一步的适应于art虚拟机,首次安装即将字节码转成机器码,进一步提高系统流畅度
- 提升流畅度的前提是以牺牲若干系统存储空间的前提下进行的,这是以空间换时间的一种策略
- 未知原因预编译优化会导致odex应用会莫名crash,并不能保证完美的兼容性
- 预编译优化的系统将不能独立编译模块push更改操作,这对于开发者来说是不利的
其他
以上文章并非自己完全独立写作出来,在这里声明出来。文章本着自己实际工作中总结的经验,在网络上无数优秀的博客中删选,总结出适合自己的、有益的内容,同时其中加上自己的一点思考,这也算是自己不断进步的印证吧!