dex2oat 参数
本文面向已经读过 DexOptHelper 入口、编译模式 和 DexMetadataHelper 的读者。要解决的问题不是“列出 dex2oat 的所有命令行开关”,而是:PMS/ART Service 里的一个 dexopt 请求,如何形成编译参数,哪些条件会覆盖它,最终哪些值真正传给 artd。
一个关键认知是:Android 17 的 system_server 不直接拼接 dex2oat --compiler-filter ... 字符串。PMS 创建 DexoptParams,ART Service 再根据包状态生成 DexoptOptions,最后通过 artd.dexopt(...) 的结构化接口交给 ART 守护进程。compilerFilter 是请求值,不一定是最终值;priorityClass 也不能从 reason 字符串直接推断,资源调度使用单独的优先级字段。
1. 参数分层
| 层 | 对象 | 主要字段 | 作用 |
|---|---|---|---|
| PMS 请求层 | DexoptParams | reason、filter、priority、flags | 描述“为什么编译、希望达到什么目标” |
| ART 包层 | DexoptOptions | reason、targetSdk、debuggable、app image、日志 | 为某个 dex 文件生成 dex2oat 选项 |
| ART 目标层 | DexoptTarget / DexInfo | dex path、ISA、CLC、DM、输出产物 | 描述“对哪个代码文件编译” |
| artd 调用层 | artd.dexopt(...) | output、filter、profile、VDEX、DM、priority、options | 执行编译并返回 ArtdDexoptResult |
2. 参数构造
源码文件:art/libartservice/service/java/com/android/server/art/model/DexoptParams.java,符号:Builder
public Builder(@NonNull String reason) {
this(reason, ArtFlags.defaultDexoptFlags(reason));
}
public Builder(@NonNull String reason,
@DexoptFlags int flags) {
mParams.mReason = reason;
setFlags(flags);
}
public Builder setCompilerFilter(@NonNull String value) {
mParams.mCompilerFilter = value;
return this;
}
public Builder setPriorityClass(
@PriorityClassApi int value) {
mParams.mPriorityClass = value;
return this;
}构造器先根据 reason 填充默认 flags,之后调用方可以覆盖 compiler filter、priority 和其他字段。reason 是策略标签,不应被当作线程优先级或资源预算;源码明确要求消费者使用 getPriorityClass() 获取优先级。
源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java,符号:getDexoptParamsByInstallRequest
String compilationReason =
mInstallScenarioHelper
.getCompilationReasonForInstallScenario(
installRequest.getInstallScenario());
var builder = new DexoptParams.Builder(compilationReason);
if (installRequest.getInstallReason()
== INSTALL_REASON_DEVICE_RESTORE
|| installRequest.getInstallReason()
== INSTALL_REASON_DEVICE_SETUP) {
builder.setPriorityClass(
ArtFlags.PRIORITY_INTERACTIVE_FAST);
}
if (installRequest.getDexoptCompilerFilter() != null) {
builder.setCompilerFilter(
installRequest.getDexoptCompilerFilter());
} else if (shouldSkipDexopt(installRequest)) {
builder.setCompilerFilter(
DexoptParams.COMPILER_FILTER_NOOP);
}
if ((installRequest.getInstallFlags()
& PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE) != 0) {
builder.setFlags(ArtFlags.FLAG_IGNORE_PROFILE,
ArtFlags.FLAG_IGNORE_PROFILE);
}
return builder.build();PMS 这一层只做安装请求级决策:安装场景变成 reason;恢复/设备 setup 提高优先级;显式 filter 覆盖默认 filter;debuggable 或 Incremental 条件在没有显式 filter 时设置 skip;ignore-profile 则是 flags。skip 是 ART Service 内部消费的 NOOP 值,不会传给 dex2oat。
3. Builder 的合法性
源码文件:art/libartservice/service/java/com/android/server/art/model/DexoptParams.java,符号:build
public DexoptParams build() {
if (mParams.mReason.isEmpty()) {
throw new IllegalArgumentException(
"Reason must not be empty");
}
if (mParams.mCompilerFilter.isEmpty()) {
mParams.mCompilerFilter =
ReasonMapping.getCompilerFilterForReason(
mParams.mReason);
} else if (!Utils.isValidArtServiceCompilerFilter(
mParams.mCompilerFilter)) {
throw new IllegalArgumentException(
"Invalid compiler filter '"
+ mParams.mCompilerFilter + "'");
}
if (mParams.mPriorityClass == ArtFlags.PRIORITY_NONE) {
mParams.mPriorityClass =
ReasonMapping.getPriorityClassForReason(
mParams.mReason);
} else if (mParams.mPriorityClass < 0
|| mParams.mPriorityClass > 100) {
throw new IllegalArgumentException(
"Invalid priority class");
}未显式设置 filter 或 priority 时,Builder 才回到 ReasonMapping 查询默认值;一旦显式设置,就只验证合法性,不再从 reason 覆盖。reason 没有对应 pm.dexopt.<reason> 属性时,build 会失败,而不是静默选择一个 filter。
源码文件:art/libartservice/service/java/com/android/server/art/model/DexoptParams.java,符号:build 的 flags 约束
if ((mParams.mFlags
& (ArtFlags.FLAG_FOR_PRIMARY_DEX
| ArtFlags.FLAG_FOR_SECONDARY_DEX)) == 0) {
throw new IllegalArgumentException(
"Nothing to dexopt");
}
if ((mParams.mFlags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
&& (mParams.mFlags
& ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0) {
throw new IllegalArgumentException(
"FLAG_SHOULD_INCLUDE_DEPENDENCIES must not set "
+ "if FLAG_FOR_PRIMARY_DEX is not set.");
}
if ((mParams.mFlags & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
if ((mParams.mFlags & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0) {
throw new IllegalArgumentException(
"FLAG_FOR_PRIMARY_DEX must be set when "
+ "FLAG_FOR_SINGLE_SPLIT is set");
}
}flags 还定义了目标范围:至少要选择 primary 或 secondary dex;dependencies 只适用于 primary;single split 不能与 secondary/dependencies 组合。这个校验发生在 ART Service 参数对象构造时,早于任何 dex2oat 进程启动。
4. reason 到默认值
源码文件:art/libartservice/service/java/com/android/server/art/ReasonMapping.java,符号:getCompilerFilterForReason、getPriorityClassForReason
public static String getCompilerFilterForReason(
@NonNull String reason) {
String value = SystemProperties.get(
"pm.dexopt." + reason);
if (TextUtils.isEmpty(value)) {
if (reason.equals(REASON_POST_UNATTENDED_REBOOT)) {
return getCompilerFilterForReason(
REASON_BG_DEXOPT);
}
if (reason.equals(REASON_PRE_REBOOT_DEXOPT_SYNC)) {
return getCompilerFilterForReason(
REASON_PRE_REBOOT_DEXOPT);
}
throw new IllegalArgumentException(
"No compiler filter for reason '"
+ reason + "'");
}
if (!Utils.isValidArtServiceCompilerFilter(value)) {
throw new IllegalStateException(
"Got invalid compiler filter '" + value + "'");
}
return value;
}默认 filter 来自系统属性,不是源码中的固定表。post-ur 默认复用 bg-dexopt,同步 pre-reboot 默认复用异步 pre-reboot reason;其他 reason 缺属性时直接失败。
public static byte getPriorityClassForReason(
@NonNull String reason) {
switch (reason) {
case REASON_INSTALL_FAST:
return ArtFlags.PRIORITY_INTERACTIVE_FAST;
case REASON_INSTALL:
case REASON_CMDLINE:
return ArtFlags.PRIORITY_INTERACTIVE;
case REASON_BG_DEXOPT:
case REASON_PRE_REBOOT_DEXOPT:
case REASON_POST_UNATTENDED_REBOOT:
case REASON_INACTIVE:
return ArtFlags.PRIORITY_BACKGROUND;
default:
throw new IllegalArgumentException(
"No priority class for reason '"
+ reason + "'");
}
}reason 与 priority 是两张表:install-fast 是 interactive-fast,普通 install/cmdline 是 interactive,后台和 inactive 是 background。不要从字符串前缀猜测 CPU 优先级,也不要把 compiler filter 的快慢等同于进程调度优先级。
5. Filter 调整
DexoptParams.getCompilerFilter() 仍然只是请求值。对每个 dex 目标,Dexopter.adjustCompilerFilter() 会执行一组只能降级的条件。
源码文件:art/libartservice/service/java/com/android/server/art/Dexopter.java,符号:adjustCompilerFilter
if ((mParams.getFlags()
& ArtFlags.FLAG_FORCE_COMPILER_FILTER) == 0) {
if (mInjector.isSystemUiPackage(
mPkgState.getPackageName())) {
String filter = getSystemUiCompilerFilter();
if (!filter.isEmpty()) {
targetCompilerFilter =
printAdjustCompilerFilterReason(
targetCompilerFilter, filter,
"the package is System UI",
dexInfo.dexPath());
}
} else if (mInjector.isLauncherPackage(
mPkgState.getPackageName())) {
targetCompilerFilter =
printAdjustCompilerFilterReason(
targetCompilerFilter, "speed-profile",
"the package is a launcher package",
dexInfo.dexPath());
}
}没有 FLAG_FORCE_COMPILER_FILTER 时,System UI 可以由 dalvik.vm.systemuicompilerfilter 覆盖,Launcher 强制使用 speed-profile;随后还可由 ART Service 的 AdjustCompilerFilterCallback 再调整。这个阶段是包策略覆盖,不是 dex2oat 自己重新选择。
源码文件:art/libartservice/service/java/com/android/server/art/Dexopter.java,符号:安全模式、CLC、embedded dex 和 ignore profile 分支
if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) {
targetCompilerFilter =
printAdjustCompilerFilterReason(
targetCompilerFilter,
DexFile.getSafeModeCompilerFilter(
targetCompilerFilter),
mPkg.isVmSafeMode()
? "the package requests VM safe mode"
: "the package is debuggable",
dexInfo.dexPath());
}
if (dexInfo.classLoaderContext() == null
&& DexFile.isOptimizedCompilerFilter(
targetCompilerFilter)) {
targetCompilerFilter = printAdjustCompilerFilterReason(
targetCompilerFilter, "verify",
"there is no valid class loader context",
dexInfo.dexPath());
}
if (mPkg.isUseEmbeddedDex()
&& DexFile.isOptimizedCompilerFilter(
targetCompilerFilter)) {
targetCompilerFilter = printAdjustCompilerFilterReason(
targetCompilerFilter, "verify",
"the package requests to use embedded dex",
dexInfo.dexPath());
}
if ((mParams.getFlags() & ArtFlags.FLAG_IGNORE_PROFILE) != 0
&& DexFile.isProfileGuidedCompilerFilter(
targetCompilerFilter)) {
targetCompilerFilter = printAdjustCompilerFilterReason(
targetCompilerFilter, "verify",
"the user requests to ignore the profile",
dexInfo.dexPath());
}从这里开始源码只允许降级,不再升级 filter。debuggable/VM safe mode、缺失 class loader context、embedded dex 和 ignore profile 都可能把 AOT 或 profile-guided filter 变成 verify。这解释了为什么请求参数显示 speed-profile,最终结果却是 verify。
6. Options 组装
源码文件:art/libartservice/service/java/com/android/server/art/Dexopter.java,符号:getDexoptOptions
private DexoptOptions getDexoptOptions(
@NonNull DexInfoType dexInfo,
boolean isProfileGuidedFilter) {
DexoptOptions options = new DexoptOptions();
options.compilationReason = mParams.getReason();
options.targetSdkVersion = mPkg.getTargetSdkVersion();
options.debuggable = mPkg.isDebuggable()
|| isAlwaysDebuggable();
options.generateAppImage = isProfileGuidedFilter
&& isAppImageEnabled();
options.hiddenApiPolicyEnabled =
isHiddenApiPolicyEnabled();
options.comments = String.format(
"app-name:%s,app-version-name:%s,"
+ "app-version-code:%d,art-version:%d",
mPkgState.getPackageName(), mPkg.getVersionName(),
mPkg.getLongVersionCode(), mInjector.getArtVersion());
options.verboseLogTags = mParams.getVerboseLogTags() != null
? mParams.getVerboseLogTags() : "";
return options;
}这里的 debuggable 是传给编译器的包属性,不等价于前面把 compiler filter 降级的判断;generateAppImage 只有 profile-guided filter 且 dalvik.vm.appimageformat 非空时才开启;hidden API policy 来自包状态。comments 进入编译诊断信息,便于把产物与包版本对应。
7. 编译输入
源码文件:art/libartservice/service/java/com/android/server/art/Dexopter.java,符号:dexoptFile
VdexPath inputVdex = getInputVdex(
getDexoptNeededResult,
target.dexInfo().dexPath(),
target.isa());
if (target.dmPath() != null
&& ReasonMapping.REASONS_FOR_INSTALL.contains(
dexoptOptions.compilationReason)) {
dexoptOptions.compilationReason =
dexoptOptions.compilationReason + "-dm";
}
ArtdDexoptResult result = mInjector.getArtd().dexopt(
outputArtifacts,
target.dexInfo().dexPath(),
target.isa(),
target.dexInfo().classLoaderContext(),
target.compilerFilter(),
profile,
inputVdex,
target.dmPath(),
priorityClass,
dexoptOptions,
artdCancellationSignal,
mParams.getLoggingFd());一次 artd.dexopt 同时携带 dex 路径、ISA、class loader context、最终 filter、profile、输入 VDEX、DM、priority 和诊断选项。DM 路径存在时,安装 reason 会附加 -dm;这只是统计信号,不保证 DM 内容一定被使用。inputVdex 为 null 也可能是因为 VDEX 位于 DM 容器中,不能解释为“没有 VDEX”。
8. 重编译比较器
参数不仅决定“怎么编译”,还决定“是否需要编译”。
源码文件:art/libartservice/service/java/com/android/server/art/Dexopter.java,符号:getDexoptTrigger
if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) {
return AidlUtils.buildDexoptTrigger(
List.of(DexoptComparator
.CUSTOM_TARGET_IS_BETTER_THAN_CURRENT),
"force recompilation");
}
if ((options.flags()
& ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) {
return AidlUtils.buildDexoptTrigger(
List.of(DexoptComparator
.COMPARING_COMPILER_FILTER_REVERSED));
}
List<Integer> comparators = new ArrayList<>();
comparators.add(DexoptComparator.COMPARING_COMPILER_FILTER);
comparators.add(DexoptComparator
.COMPARING_PRIMARY_BOOT_IMAGE_STATUS);
comparators.add(DexoptComparator.COMPARING_EXTRACTION_STATUS);
return AidlUtils.buildDexoptTrigger(comparators);FLAG_FORCE 直接要求目标优于当前产物;downgrade 反向比较 compiler filter;普通请求比较 filter、boot image 状态和 extraction 状态。即使目标 filter 比当前更高,如果这些比较器判断没有必要,也可能返回 skipped。
9. 结果与产物清理
源码文件:art/libartservice/service/java/com/android/server/art/Dexopter.java,符号:dexoptFile 返回后的处理
if (!result.cancelled && !mInjector.isPreReboot()) {
mInjector.getArtd().deleteRuntimeArtifacts(
AidlUtils.buildRuntimeArtifactsPath(
mPkgState.getPackageName(),
target.dexInfo().dexPath(),
target.isa()));
}
return result;编译完成后,非 pre-reboot 场景会删除旧 runtime artifacts,即使它们理论上仍可用;目的是让 dex 文件进入明确的 dexopt 状态并尽早释放空间。pre-reboot 必须保留旧产物直到重启,因此这条清理分支被跳过。清理与 compiler filter 参数是两个独立动作。
10. 失败边界
| 层次 | 失败示例 | 行为 |
|---|---|---|
| Builder | 空 reason、非法 filter、目标 flags 为空 | 抛 IllegalArgumentException |
| 默认配置 | pm.dexopt.<reason> 缺失或非法 | 抛 IllegalArgumentException/StateException |
| 调整阶段 | 无 CLC、embedded dex、ignore profile | 降级为 verify |
| profile 阶段 | 无有效 profile | profile-guided filter 降为 verify |
| artd 编译 | dex2oat 启动或执行失败 | 返回失败 ArtdDexoptResult |
| 取消 | IArtdCancellationSignal 被取消 | 返回 cancelled,不提交临时产物 |
普通 dexopt 失败通常通过 DexoptResult 传回 PMS,而不是让安装线程直接抛出;未预期的 system_server 异常才会被 DexOptHelper 记录为 fatal。参数文章中最重要的诊断原则是:先区分“请求不合法”“目标被调整”“编译器失败”和“比较器判定 skipped”。
11. 源码验证
可以用以下真实测试断言来校准阅读结论:
源码文件:art/libartservice/service/javatests/com/android/server/art/BackgroundDexoptJobTest.java
when(mArtManagerLocal.dexoptPackages(
any(), any(), any(), any(), any()))
.thenThrow(RuntimeException.class);
mBackgroundDexoptJob.onStartJob(
mJobService, mJobParameters);
verify(mJobService).jobFinished(
any(), eq(false) /* wantsReschedule */);该测试证明后台 job 的 fatal result 不会由 jobFinished 主动要求周期内重调度;它不证明所有 ART 编译失败都属于 fatal,因为正常 dexopt 失败应以结果状态表达。
源码文件:art/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
var mainResult = DexoptResult.create(
"speed-profile", "bg-dexopt", ...);
// Profile-guided but skipped packages are selected for
// the supplementary force-merge pass.相关测试夹具验证 bg-dexopt 主批次中 profile-guided 且 skipped 的包会进入 supplementary pass。这证明“参数和结果共同决定后续 pass”,不证明每次 skipped 都会重编译,因为 FLAG_FORCE_MERGE_PROFILE 和 profile 是否有变化仍由 ART 判断。
12. 阅读路径
从一次安装请求继续读源码时,建议沿这条路径:
DexOptHelper.getDexoptParamsByInstallRequest:确定 reason、显式 filter 和 install flags。DexoptParams.Builder.build:检查默认值、flags 和 split 组合是否合法。ReasonMapping:查看pm.dexopt.<reason>、priority 和 concurrency 的来源。Dexopter.adjustCompilerFilter:观察包状态和安全条件如何只允许降级。Dexopter.getDexoptOptions:补齐 targetSdk、app image、hidden API 和诊断信息。Dexopter.dexoptFile:核对 profile、VDEX、DM、最终 filter 和 priority 如何进入artd.dexopt。getDexoptTrigger与结果处理:解释为什么请求可能 skipped、performed、failed 或 cancelled。
完成这条阅读后,看到一条 dexopt 日志时应能回答:日志中的 reason 是谁设置的,compiler filter 是请求值还是调整后的值,DM/profile/VDEX 走了哪条输入通道,以及产物为什么被重新编译或被判定为无需处理。
