ODEX(Optimized DEX)和 VDEX(Verifiable DEX)是 Android 运行时性能的关键组件。从 APK 安装时触发的 dex2oat 编译,到 OAT 文件的结构、编译过滤器(compiler filter)的选择,再到系统 OTA 升级时的全局 dexopt——本文完整解析 Android DEX 优化流程。
一、从 DEX 到 OAT:编译流水线
1.1 整体流程
APK 中的 classes.dex(Dalvik Executable 字节码) |
AOSP 核心路径:
art/dex2oat/— dex2oat 编译器源码art/runtime/oat_file.h和art/runtime/oat_file.cc— OAT 文件结构art/runtime/oat_file_assistant.h— OAT 文件查找和验证art/libdexfile/dex/— DEX 文件解析库
1.2 dex2oat 的命令行
# 典型调用(由 installd 发起) |
1.3 dex2oat 的初始化与编译
// art/dex2oat/dex2oat.cc |
// art/dex2oat/dex2oat.cc |
二、OAT 文件结构
2.1 OAT 文件头
// art/runtime/oat.h |
2.2 OatDexFile——每个 DEX 文件的元数据
// art/runtime/oat_file.h |
2.3 OatClass——每个类的编译状态
// art/runtime/oat_file.h |
2.4 OatQuickMethodHeader——每个方法的编译代码头
// art/runtime/oat_quick_method_header.h |
三、VDEX 文件格式
Android 8.0 引入了 VDEX 格式,包含:
- 未压缩的 DEX 文件:供运行时快速加载,无需从 APK zip 中解压
- Quickening 信息:预优化的字节码(如将虚方法调用的 method_idx 替换为 vtable index)
- 验证信息:DEX 字节码验证结果,运行时加载时可直接信任
VDEX 的设计目标:将 DEX 验证和部分优化工作从 APK 首次启动时(Runtime)前置到安装时(dex2oat),从而显著减少应用的冷启动时间。
// art/runtime/vdex_file.h |
四、Compilation Filter(编译过滤器)
dex2oat 支持多种编译过滤器,控制 AOT 编译的粒度。由 compiler-filter 参数指定:
| Filter | 行为 | 适用场景 |
|---|---|---|
| verify | 仅验证 DEX 字节码,不编译任何方法 | 开发调试阶段 |
| quicken | 优化字节码(Quickening),不编译为机器码 | 存储空间受限的设备 |
| speed-profile | 基于 Profile 指导编译:热方法 AOT,冷方法解释/JIT | 生产环境推荐(平衡性能与空间) |
| speed | 编译所有方法(full AOT) | 对性能极致要求的场景 |
| everything | 编译所有方法 + 运行时特殊路径 | 极少使用(空间开销巨大) |
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java |
// art/dex2oat/compiler_filter.h |
五、系统 OTA 升级时的 dexopt
当系统 OTA 升级完成后,framework 的 boot classpath 发生变化,需要重新编译所有应用的 OAT 文件。这个操作由 ota-dexopt 触发:
// frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java |
系统升级后的 dexopt 路径:
ota-dexopt或bg-dexopt脚本被触发installd收到指令,遍历/data/app/下所有应用- 对每个应用调用
dex2oat,根据其当前的 compiler filter 重新编译 - 新生成的
.odex和.vdex文件覆盖旧的
六、安装时 dexopt vs OTA dexopt 的区别
| 维度 | 安装时 dexopt | OTA dexopt |
|---|---|---|
| 触发时机 | APK 安装完成时(PackageInstallerSession.commit) | 系统 OTA 升级后 |
| 范围 | 仅新安装/更新的应用 | 所有已安装应用 |
| 编译过滤器 | 新应用默认 speed-profile | 继承原有 filter |
| 执行环境 | 可与前台安装并行(后台线程) | 通常在充电且空闲时执行 |
| 实现类 | PackageManagerService.performDexopt | BackgroundDexOptService / otapreopt |
七、核心面试题
Q1:什么是 Quickening?它和 AOT 编译有什么区别?
Quickening 是在 DEX 字节码层面进行的优化,而 AOT 是将 DEX 字节码编译为机器码。Quickening 包括:将虚方法调用的 method_idx 替换为 vtable index、优化字段访问为偏移量查找、内联简单的 getter/setter 字节码。它不需要生成机器码,因此编译速度极快,文件体积增长小。AOT 编译则完全生成本地机器指令,执行速度快但编译时间长、输出文件大。Android 采用分层编译:默认 Quickening + JIT(热方法被 JIT 编译为机器码),通过 profile 记录热点,在后台 dexopt 时对这些热方法做 AOT。
Q2:VDEX 文件相比纯 OAT 有什么优势?为什么不直接全部放 OAT?
VDEX 包含未压缩的 DEX 文件和 quickening 信息,与 OAT 分离有几个好处:(1) 当系统 OTA 升级(boot classpath 变化)时,OAT 需要重编译(因为 AOT 代码内联了 Framework 方法),但 VDEX 中的 DEX 和 quickening 信息仍然有效,可以继续用来解释执行。(2) 未压缩的 DEX 可以直接 mmap,无需从 APK zip 中解压,减少了冷启动时的 I/O。(3) VDEX 分离也方便独立更新 DEX 验证结果。
Q3:dex2oat 在安装时被触发,如何确保不影响系统响应?
dex2oat 不是在 SystemServer 进程中直接执行,而是通过 installd 守护进程 fork 出独立的 dex2oat 进程执行。installd 以低 I/O 优先级(ionice)和低 CPU 优先级(nice)运行 dex2oat,而且 PackageManagerService 使用 BackgroundDexOptService 管理编译队列,确保同一时间只运行有限数量的 dex2oat 进程(通常 1-2 个),避免资源争用影响前台应用。
AOSP 核心路径参考:
art/dex2oat/dex2oat.cc— dex2oat 编译器入口art/dex2oat/compiler_filter.h— 编译过滤器定义art/runtime/oat_file.h/art/runtime/oat_file.cc— OAT 文件定义art/runtime/oat_header.h— OAT 文件头结构art/runtime/vdex_file.h— VDEX 文件定义art/runtime/oat_file_assistant.h— OAT 文件查找与验证art/libdexfile/dex/— DEX 文件解析库frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.javaframeworks/native/cmds/installd/— installd 守护进程



