如果我们在user版本用mm编译apk时,会在out目录下生成.apk和一个oat目录,里边有.odex文件。
但是发现直接用adb push ,把生成的整个app目录<包括apk、oat、lib> 推到app安装目录,并不会生效,而是需要重启之后才会生效。这个问题纠结了很久,直到最近看了关于预编译的文章,至于为什么,下面再讲。
一般开发时需要mm编译出一个完整的apk,直接用adb进行安装就可以进行调试。这样的话我们有两个选择,一个选择是切到非user版本坐编译 ,另外一种方案就是在当前的需要编译的app目录的.mk文件中加入如下代码关闭对于当前APK的预优化处理即可:
1 | LOCAL_DEX_PREOPT := false |
那么为什么user版本会对apk进行预优化处理呢,在device.mk里面 或者device下面别的.mk文件里面会有如下代码:
1 | ifeq ($(TARGET_BUILD_VARIANT), user) |
什么是预优化。
对于Android而言,预优化也就是把Android在launch或者runtime时候所需要做的一些事儿,把这些事儿提前到编译期来达到Android启动速度的加速和APP运行速度的加速。
Android预优化的原理。
在5.0之前 Android一直用的DVM虚拟机,到了5.0换成了ART。
1 | (1)JVM:JVM虚拟机运行的是java字节码。Java文件到JVM的过程是: |
我们能看到,从java文件到虚拟机执行代码,ART比DVM多了oat的过程。
ART所使用的AOT(Ahead-Of-Time) 编译,在首次安装应用的时候,字节码预编译成机器码存在本地。
DVM是典型的JIT(Just-In-Time) ,应用每次运行的时候,字节码都需要即时的编译转换成机器码再执行。
那么对于ART而言就省去了解析字节码的过程,占用内存也会相应的减少,从而提升APP的运行效率。
应用程序的安装有两种情况,第一:首次启动系统时安装;第二:系统启动完成后安装。在系统首次启动的场景中,系统会对/system/app、/system/priv-app、/data/app目录下的所有APK进行dex字节码到本地机器码的翻译,同样也会对/system/framework目录下的APK或者JAR文件,以及这些APK所引用的外部JAR,进行dex字节码到本地机器码的翻译。这样可以保证除了应用之外,系统中使用Java来开发的系统服务,也会统一地从dex字节码翻译成本地机器码。详细内容请移步老罗的博客Android ART运行时无缝替换Dalvik虚拟机的过程分析。
JVM、DVM、ART虚拟机了解
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时,Java类会被编译成一个或者多个字节码文件(.class),通过dx工具CLASS文件转换成一个DEX(Dalvik Executable)文件。
通常情况下,我们看到的Android应用程序实际上是一个以.apk为后缀名的压缩文件。我们可以通过压缩工具对apk进行解压,解压出来的内容中有一个名为classes.dex的文件。那么我们首次开机的时候系统需要将其从apk中解压出来保存在data/app目录中。
如果当前运行在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同样会在首次进入系统的时候调用/system/bin/dexopt工具来将dex字节码翻译成本地机器码,保存在data/dalvik-cache下。
那么这里需要注意的是,无论是对dex字节码进行优化,还是将dex字节码翻译成本地机器码,最终得到的结果都是保存在相同名称的一个odex文件里面的,但是前者对应的是一个dey文件(表示这是一个优化过的dex),后者对应的是一个oat文件(实际上是一个自定义的elf文件,里面包含的都是本地机器指令)。简单来说无论是Art模式,还是DVM,优化的结果都是一个odex文件,只是这两种odex文件有着本质的区别(一个是dey字节码,一个是oat机器码)。之所以这么设计,主要通过这种方式,原来任何通过绝对路径引用了该odex文件的代码就都不需要修改了,可以理解为这是art与dalvik兼容的结果。
由于在系统首次启动时会对应用进行安装,那么在预置APK比较多的情况下,将会大大增加系统首次启动的时间。从前面的描述可知,既然无论是DVM还是ART,对DEX的优化结果都是保存在一个相同名称的odex文件,那么如果我们把这两个过程在ROM编译的时候预处理提取Odex文件将会大大优化系统首次启动的时间。
文章开头说的,更新了apk之后需要重启才能生效,是因为android系统启动的时候会优化系统的app,所以APK的java代码被缓存到手机本地了,push user下编译的apk运行时依然运行的是/data/dalvik-cache目录 中缓存的code。