赞
踩
我们知道Android的程序虽然也是使用Java/Kotlin语言编码,并生成.class字节码,但并不能直接运行在JVM上,而是运行在自己的VM上。而Android程序之所以不能在JVM上运行的根本原因是.class字节码文件并不是Android的最终可执行文件(执行效率问题),而是一个过渡产物,最终会生成dex文件在Android VM上执行。
Android VM大体分为两种: Dalvik 虚拟机
和 ART虚拟机
。
JIT/AOT
混合编译模式。Dalvik是Google公司自己设计用于Android平台的虚拟机,它是Android平台的重要组成部分,支持
dex格式
(Dalvik Executable)的Java应用程序的运行。dex格式是专门为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Google对其进行了特定的优化,经过优化的Dalvik,具有高效、简洁、节省资源的特点,同时还允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
寄存器
,而 JVM 基于栈
。Android 2.2之前,Dalvik虚拟机是通过解释器 (解释器逐条读入字节码 -> 逐条翻译成机器码 -> 执行机器码
)来执行程序的,效率低。针对这个问题,引进了JIT(即时编译器)技术。它是一种优化手段。
JIT技术
:将解释过的机器码缓存起来,下次再执行时到这个方法的时候,则直接从缓存里面取出机器码来执行。减少了读取字节码和翻译字节码的操作。以此来提高效率。JIT技术的引入使得Dalvik的性能提升了3~6倍。
注意:
并不是所有执行过的代码对应的机器码都会被缓存起来。而是只有被认定为热点代码(Hot Spot Code) 的代码才会。这里所指的热点代码主要有两类,包括:
- 被多次调用的方法
- 被多次执行的循环体(虽然只是循环体被多次执行,但仍是将整个方法的机器码缓存起来)。
缺点:
JIT技术的缺点:
- 每次重新启动引用都需要重新编译。
- 运行时比较耗电。
ART虚拟机在Android 5.0开始替换Dalvik虚拟机,其处理应用程序执行的方式不同于Dalvik虚拟机,它不使用JIT而是使用了AOT(Ahead-Of-Time),也就是提前编译技术。并对垃圾收集器也进行了改进和优化。
预先编译机制(AOT
)可提高应用的性能。同时ART 还具有比 Dalvik 更严格的安装时验证。
AOT(提前编译技术):
简单来说就是提前将字节码转换成本地机器码,然后存储在本地磁盘上,运行时可以直接执行,避免了Dalvik时期的应用运行时再来解释字节码。运行时效率大大提高。
在Android 7.0 之前,Android系统安装Apk时,会进行一次全量预编译,将字节码预先编译成本地机器码,生成 oat文件
,并存储在本地磁盘上。这样在App每次运行时就不需要重新编译,可以直接使用编译好本地机器码,运行效率大大提升。但是这也使得安装应用的时间大大增加,于是在Android7.0及之后,又重新引进了JIT技术,形成JIT/AOT混合编译模式。
混合编译的特点:
使用JIT编译器来对AOT编译器进行补充,降低了Apk安装的时间,提升了运行时性能,节省了存储空间,加快应用运行速度。
小结:
Android 7.0以前,采用AOT全量预编译,Apk安装时预编译dex生成对应的机器码文件。但预编译量大导致Apk安装时间长。
Android 7.0及之后,采用JIT/AOT混合编译模式,根据对应的profile在空闲时进行AOT预编译。
解释器: 逐条读入字节码 -> 逐条翻译成机器码 -> 执行机器码,重复执行同一代码时需要重新翻译执行。
JIT编译器: 对运行时的热点代码(热点代码)进行编译,且缓存在内存中,当下次继续执行时,直接从内存中获取,减少重复编译。
AOT编译器: 在运行前将字节码转换为机器码,在运行时直接运行转换后的机器码。
APK 文件其实是 zip 格式,在Window平台上可以直接将后缀格式改为zip进行解压。解压后的目录如下图所示:
文件名 | 说明 |
---|---|
META-INF/ | 信息描述,签名等用途。编译生成一个apk包时,会对所有要打包的文件做一个校验计算,并把计算结果放在META-INF目录下。而在Android手机上安装apk包时,应用管理器会按照同样的算法对包里的文件做校验,如果校验结果与META-INF下的内容不一致,系统就不会安装这个apk。这就保证了apk包里的文件不能被随意替换 |
res/ | 存放资源文件 |
libs/ | 存放的是 ndk 编出来的 so 库 |
AndroidManifest.xml | 程序全局清单文件 |
classes.dex | dalvik 字节码 |
resources.ars | 编译后的二进制资源文件,主要是对应的索引 |
assets/ | 保留工程中assets目录,其他工程下的、jar包中的assets也会合并到该assets目录下。 |
dex 文件是可被Dalvik虚拟机识别并执行的文件, Dalvik 会执行 .dex 文件中的 dalvik 字节码,但一般Dalvik在执行dex优化后的文件(即odex文件)。
dex文件特点:
- dex文件是Android系统中的一种文件,是一种特殊的数据格式,和Apk、jar等格式文件类似。
- 文件更加紧凑:dex文件是能够被DVM识别,加载并执行的文件格式。相比于Jar文件,dex会把所有包含的信息整合在一起,减少冗余信息,从而降低了加载文件时的I/O耗时,提高类的查找速度。
- dex文件包含应用程序的全部操作指令和运行时数据。
- 相对于PC上的JVM能运行
.class
文件,Android上的Dalvik虚拟机能运行.dex
文件。
.dex文件和 .class文件的格式对照:
dex 文件结构:
当Android系统启动一个Apk时,会通过
dexopt
工具对dex进行优化。dexopt
的执行过程是在第一次加载dex文件的时候执行的。这个过程会生成一个odex文件,即Optimised Dex (执行odex的效率会比直接执行Dex文件的效率要高很多)。但早期Android系统中, dexopt 有一个问题(即65535问题)。dexopt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型
(2^16=65536)来保存的,导致了方法id的数目不能够超过65536个。
背景: 对Android dex文件进行优化来说,需要注意的一点是dex文件的结构是紧凑的,但是我们还是要想方设法进行运行速度的提高,因此我们仍然需要对dex文件进一步优化。
odex文件的使用场景:
安装阶段: Apk在安装时,系统会进行验证和优化,目的是为了校验代码合法性及优化代码执行速度。当验证和优化后,系统会从Apk中提取dex文件进行优化,并将优化后的产物(
odex文件
)保存到data/dalvik-cache
目录下。
运行阶段: 当运行Apk的时候,会直接加载odex文件,避免重复验证和优化,加快了Apk的响应时间。
odex 文件的生成过程:
Android 5.0之前:Dalvik虚拟机
Dalvik虚拟机会在执行dex文件前对dex文件做优化,生成可执行文件odex,保存到
data/dalvik-cache
目录,最后把Apk文件中的dex文件删除。
注意:
此时生成的odex文件后缀依然是dex ,它是一个dex文件,里面仍然是字节码,而不是本地机器码。
Android5.0 <= Version < Android 8.0 (Android O):ART虚拟机
Android5.0之后使用ART虚拟机,ART虚拟机使用AOT预编译生成oat文件。oat文件是ART虚拟机运行的文件,是ELF格式二进制文件。oat文件包含dex和编译的本地机器指令,因此比Android5.0之前的odex文件更大。
oat文件生成过程:
- App在首次安装的时候,dex2oat 工具默认会把 dex文件翻译成本地机器指令,生成ELF格式的OAT文件,并将其放在了
/data/dalvik-cache
或/data/app/packagename/
目录下,此时oat文件后缀格式为odex。- ART加载oat文件后不需要经过处理就可以直接运行,它在编译时就从字节码装换成机器码了,因此运行速度更快。
Dalvik虚拟机执行程序dex文件前,系统会对dex文件做优化,生成可执行文件odex,保存到data/dalvik-cache
目录,最后把apk文件中的dex文件删除。 (注意:此时生成的odex文件后缀依然是dex
,它是一个dex文件,里面仍然还是字节码,而不是本地机器码。)
注意:
Android5.0及之后版本生成的 oat文件后缀还是odex,但是已经不是android5.0 及之前版本的文件格式,而是ELF格式封装的本地机器码。可以认为oat在dex上加了一层壳,可以从oat里提取出dex。
Android O及之后(>=Android 8.0):ART虚拟机
Android 8.0及之后版本,dex2oat会直接生成两个oat文件 (即
vdex文件
和odex文件
)。其中 odex 文件是从vdex 文件中提取了部分模块生成的一个新的可执行二进制码文件,odex 从vdex 中提取后,vdex 的大小就减少了。
文件生成过程:
- App在首次安装的时候,odex 文件就会生成在
/system/app/<packagename>/oat/
下。- 在系统运行过程中,虚拟机将其 从
/system/app
下 copy 到/data/davilk-cache/
下。- odex + vdex = Apk 的全部源码 (vdex 并不是独立于odex 的,文件 odex + vdex 才代表一个Apk )。
odex 的优点和缺点:
优点:
- 启动快: 省去了系统第一次启动应用时从Apk文件中读取dex文件,并对dex文件做优化的过程。和
- 对RAM的占用(Apk文件中的dex如果不删除,同一个应用就会存在两个dex文件:apk中和
data/dalvik-cache
目录下)。- 安全性:防止第三方用户反编译系统的软件(odex文件是跟随系统环境变化的,改变环境会无法运行;而apk文件中又不包含dex文件,无法独立运行)
劣势:
- 优化后的odex文件大小通常是原dex文件的1~4倍 (空间换时间)。
vdex文件是 Android O (Android 8.0) 新增的格式包,其目的是为了降低dex2oat时间。
dex2oat的触发场景:
- 当系统OTA (系统升级) 后,用户自己安装的应用是不会发生任何变化的,但 framework 代码已经发生了变化,因此就需要重新对这些应用也做dex2oat。如果没有vdex文件,则需要重新校验Apk里dex文件合法性;如果存在vdex文件,就可以省略校验的过程,节省一部分时间。
- 当App的
JIT Profile
信息变化时,background dexopt会在后台重新做dex2oat,因为有了vdex,这个时候也可以直接跳过dex文件的校验流程。
dex 文件直接转化的可执行二进制码文件:
/system/app/<packagename>/oat/
下。/system/app
下 copy 到 /data/davilk-cache/
下。art文件是由虚拟机执行odex文件后,记录虚拟机执行Apk启动的常用函数地址信息后生成出来的文件(记录函数地址信息方便寻址),目的 是用于加快应用启动速度。通常会在data/dalvik-cache/
目录中保存常用的jar包的相关地址记录。
/system/app/<packagename>/oat/
下,以后也不会。/data/davilk-cache/
由虚拟机生成 art文件(art文件生成
)。/system/app
下的odex 和 vdex 会无效,即使你删除,apk也会正常运行。/system/app
下Apk file ,会触发 PMS 扫描时下发 force_dex 的flag ,强行生成新的vdex 文件 ,覆盖之前的vdex 文件,由于某种机制,这个新vdex 文件会copy到 /data/dalvik-cache/
下,于是 art 文件也变化了。ART虚拟机运行的是oat文件,oat文件是一种Android私有ELF文件格式,oat文件包含有从dex文件翻译而来的本地机器指令,还包含有原来的dex文件内容
(如下图所示),因此oat文件比odex文件更大。APK在安装的过程中,会通过dex2oat工具生成一个OAT文件(文件后缀还是odex)。对于apk来说,oat文件实际上就是对odex文件的包装,即oat=odex。
注意: Android5.0 及之后的版本,oat文件的后缀还是odex,但是已经不是android5.0 之前的文件格式,而是ELF格式封装的本地机器码。可以认为oat在dex上加了一层壳,可以从oat里提取出dex。
oat的文件结构:
dex2oat的过程如下图所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。