目录
  1. 1. 一、从 DEX 到 OAT:编译流水线
    1. 1.1. 1.1 整体流程
    2. 1.2. 1.2 dex2oat 的命令行
    3. 1.3. 1.3 dex2oat 的初始化与编译
  2. 2. 二、OAT 文件结构
    1. 2.1. 2.1 OAT 文件头
    2. 2.2. 2.2 OatDexFile——每个 DEX 文件的元数据
    3. 2.3. 2.3 OatClass——每个类的编译状态
    4. 2.4. 2.4 OatQuickMethodHeader——每个方法的编译代码头
  3. 3. 三、VDEX 文件格式
  4. 4. 四、Compilation Filter(编译过滤器)
  5. 5. 五、系统 OTA 升级时的 dexopt
  6. 6. 六、安装时 dexopt vs OTA dexopt 的区别
  7. 7. 七、核心面试题
【深入内核篇】ODEX流程

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 字节码)


┌─────────────────────────────────────┐
│ dex2oat 编译器 │
│ ┌─────────────────────────────────┐ │
│ │ 1. 解析 DEX 文件 │ │
│ │ 2. 验证字节码 │ │
│ │ 3. 优化字节码(Quickening) │ │
│ │ 4. 编译为机器码(AOT Compilation)│ │
│ │ 5. 输出 OAT + VDEX 文件 │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘


┌───────────────────────────────────────────────┐
│ /data/dalvik-cache/ (或 /data/app/<pkg>/oat/)│
│ ├── <pkg>.odex (OAT 文件) │
│ └── <pkg>.vdex (VDEX 文件) │
└───────────────────────────────────────────────┘

AOSP 核心路径:

  • art/dex2oat/ — dex2oat 编译器源码
  • art/runtime/oat_file.hart/runtime/oat_file.cc — OAT 文件结构
  • art/runtime/oat_file_assistant.h — OAT 文件查找和验证
  • art/libdexfile/dex/ — DEX 文件解析库

1.2 dex2oat 的命令行

# 典型调用(由 installd 发起)
dex2oat \
--dex-file=/data/app/com.example.app-xxx/base.apk \
--oat-file=/data/app/com.example.app-xxx/oat/arm64/base.odex \
--compiler-filter=speed-profile \
--instruction-set=arm64 \
--android-root=/system \
--app-image-file=/data/app/com.example.app-xxx/oat/arm64/base.art

1.3 dex2oat 的初始化与编译

// art/dex2oat/dex2oat.cc
int main(int argc, char** argv) {
// 1. 解析命令行参数
auto dex2oat = std::make_unique<Dex2Oat>(&parser);

// 2. 创建 Runtime(ART 运行时实例,用于编译)
if (!CreateRuntime(dex2oat.get(), &args)) {
return EXIT_FAILURE;
}

// 3. 加载 DEX 文件
dex2oat->LoadClassProfileDescriptors();

// 4. 执行编译
dex2oat->Compile();

// 5. 输出 OAT 文件
dex2oat->WriteOatFiles();

return EXIT_SUCCESS;
}
// art/dex2oat/dex2oat.cc
void Dex2Oat::Compile() {
// 遍历 class_defs,对每类进行编译
TimingLogger::ScopedTiming t("dex2oat Compile", timings_);

// 打开所有 DEX 文件
OpenDexFiles();

// 根据 compiler filter 决定编译粒度
// speed / speed-profile / quicken / verify / everything
CompilerFilter::Filter filter = compiler_options_->GetCompilerFilter();

// 对 DEX 中的类进行编译
driver_->CompileAll(class_loader, dex_files_, timings_);
}

二、OAT 文件结构

2.1 OAT 文件头

// art/runtime/oat.h
class OatHeader {
uint8_t magic_[4]; // "oat\n"
uint8_t version_[4]; // OAT 版本号(如 0x00e3)
uint32_t adler32_checksum_; // 文件完整性校验

InstructionSet instruction_set_; // 目标指令集(arm64, x86_64 等)
uint32_t instruction_set_features_bitmap_;

uint32_t dex_file_count_; // 包含的 DEX 文件数
uint32_t oat_dex_files_offset_; // OatDexFile 数组的偏移量
uint32_t executable_offset_; // 可执行代码开始偏移
uint32_t jni_dlsym_lookup_offset_;// JNI dlsym 查找表偏移
uint32_t quick_generic_jni_trampoline_offset_;
uint32_t quick_imt_conflict_trampoline_offset_;
uint32_t quick_resolution_trampoline_offset_;
uint32_t quick_to_interpreter_bridge_offset_;

int32_t image_patch_delta_; // 如果是 boot.oat,此字段非零

uint32_t image_file_location_oat_checksum_;
uint32_t image_file_location_oat_data_begin_;

uint32_t key_value_store_size_; // 键值对元数据存储大小
// 后面跟着 key_value_store_ 数据
};

2.2 OatDexFile——每个 DEX 文件的元数据

// art/runtime/oat_file.h
class OatDexFile {
// 原始 DEX 文件的路径
std::string dex_file_location_;

// DEX 文件的 checksum
uint32_t dex_file_location_checksum_;

// 对应 DEX 文件在 OAT 中的偏移量
uint32_t dex_file_offset_;

// 此 DEX 文件中所有类的 OatClass 查找表
// 格式:lookup_table[num_class_defs]
// lookup_table[i] 指向第 i 个类的 OatClass 的状态/偏移量
const uint32_t* lookup_table_data_;
};

2.3 OatClass——每个类的编译状态

// art/runtime/oat_file.h
enum class OatClassType : uint8_t {
kOatClassAllCompiled = 0, // 所有方法都编译为 native 代码
kOatClassSomeCompiled = 1, // 部分方法编译
kOatClassNoneCompiled = 2, // 无编译(解释执行)
kOatClassMax = 3,
};

class OatClass {
OatClassType type_;
uint32_t methods_pointer_offset_; // 方法偏移表的偏移量
// 如果 type_ == kOatClassSomeCompiled:
// 后面跟着方法和代码的 bitmap + offset 表
};

2.4 OatQuickMethodHeader——每个方法的编译代码头

// art/runtime/oat_quick_method_header.h
class OatQuickMethodHeader {
uint32_t vmap_table_offset_; // 虚拟映射表偏移
uint32_t code_size_; // 编译代码的大小
// 后面跟着实际的机器码
};

三、VDEX 文件格式

Android 8.0 引入了 VDEX 格式,包含:

  1. 未压缩的 DEX 文件:供运行时快速加载,无需从 APK zip 中解压
  2. Quickening 信息:预优化的字节码(如将虚方法调用的 method_idx 替换为 vtable index)
  3. 验证信息:DEX 字节码验证结果,运行时加载时可直接信任

VDEX 的设计目标:将 DEX 验证和部分优化工作从 APK 首次启动时(Runtime)前置到安装时(dex2oat),从而显著减少应用的冷启动时间。

// art/runtime/vdex_file.h
class VdexFile {
// VDEX 文件头
struct VdexFileHeader {
uint8_t magic_[4]; // "vdex"
uint8_t version_[4]; // VDEX 版本号
uint32_t number_of_sections_;
// sections_: 包含 DEX 文件段、Quickening 信息段、验证依赖段
};

// 获取未压缩的 DEX 文件
const uint8_t* GetNextDexFileData(const uint8_t* cursor) const;

// 获取 Quickening 信息
const uint8_t* GetQuickeningInfo() const;
};

四、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
// 安装时指定编译过滤器
void performDexopt(List<PackageSetting> pkgSettings, ...) {
String compilerFilter;
if (isSystemApp) {
compilerFilter = "speed"; // 系统应用全 AOT
} else if (isProfileGuidedCompile) {
compilerFilter = "speed-profile"; // 第三方应用 profile guided
} else {
compilerFilter = "quicken"; // 仅 quickening
}
// 调用 installd 执行
mInstaller.dexopt(pkgPath, ..., compilerFilter, ...);
}
// art/dex2oat/compiler_filter.h
enum class Filter {
kVerify, // 仅验证,无任何优化
kQuicken, // Quickening 优化
kSpeedProfile, // 基于 profile 的 AOT
kSpeed, // 全 AOT
kEverything, // 全编译
};

// 判断某个方法是否需要 AOT 编译
bool CompilerFilter::IsAotCompilationEnabled(Filter filter) {
return filter >= Filter::kSpeedProfile;
}

五、系统 OTA 升级时的 dexopt

当系统 OTA 升级完成后,framework 的 boot classpath 发生变化,需要重新编译所有应用的 OAT 文件。这个操作由 ota-dexopt 触发:

// frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java
public class BackgroundDexOptService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 在充电 + 空闲时,后台执行 dexopt
// 常用于 OTA 升级后为所有应用重新编译
mDexOptHandler.post(() -> {
performBackgroundDexOpt(packageName);
});
return START_STICKY;
}
}

系统升级后的 dexopt 路径:

  1. ota-dexoptbg-dexopt 脚本被触发
  2. installd 收到指令,遍历 /data/app/ 下所有应用
  3. 对每个应用调用 dex2oat,根据其当前的 compiler filter 重新编译
  4. 新生成的 .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.java
  • frameworks/native/cmds/installd/ — installd 守护进程
打赏
  • 微信
  • 支付宝

评论