Skip to content

dex2oat 参数

追踪 DexoptParams 到 artd.dexopt,分析 compiler filter、优先级、flags 和降级条件的传递路径。

基于android-17.0.0_r1
AndroidPMSDexOptART

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 请求层DexoptParamsreason、filter、priority、flags描述“为什么编译、希望达到什么目标”
ART 包层DexoptOptionsreason、targetSdk、debuggable、app image、日志为某个 dex 文件生成 dex2oat 选项
ART 目标层DexoptTarget / DexInfodex 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

java
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

java
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

java
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 约束

java
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

java
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 缺属性时直接失败。

java
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

java
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 分支

java
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

java
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

java
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

java
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 返回后的处理

java
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 阶段无有效 profileprofile-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

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

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. 阅读路径 ​

从一次安装请求继续读源码时,建议沿这条路径:

  1. DexOptHelper.getDexoptParamsByInstallRequest:确定 reason、显式 filter 和 install flags。
  2. DexoptParams.Builder.build:检查默认值、flags 和 split 组合是否合法。
  3. ReasonMapping:查看 pm.dexopt.<reason>、priority 和 concurrency 的来源。
  4. Dexopter.adjustCompilerFilter:观察包状态和安全条件如何只允许降级。
  5. Dexopter.getDexoptOptions:补齐 targetSdk、app image、hidden API 和诊断信息。
  6. Dexopter.dexoptFile:核对 profile、VDEX、DM、最终 filter 和 priority 如何进入 artd.dexopt。
  7. getDexoptTrigger 与结果处理:解释为什么请求可能 skipped、performed、failed 或 cancelled。

完成这条阅读后,看到一条 dexopt 日志时应能回答:日志中的 reason 是谁设置的,compiler filter 是请求值还是调整后的值,DM/profile/VDEX 走了哪条输入通道,以及产物为什么被重新编译或被判定为无需处理。