Skip to content

dexopt 流程总览

分析 PMS 中 dexopt 的启动、安装、跳过条件和 ART Service 结果回传。

AndroidPMSDexOptART

dexopt 流程总览 ​

PMS 不直接执行 dex2oat 命令。Android 17 由 DexOptHelper 选择触发原因和安装参数,再通过 ArtManagerLocal 调用 ART Service;安装场景使用异步 Future,启动场景使用同步 boot dexopt。dexopt 失败通常不会使普通 APK 安装失败,而是记录结果并允许运行时解释执行。

1. 触发场景 ​

场景入口reason
首次启动performPackageDexOptUpgradeIfNeededREASON_FIRST_BOOT
OTA 后启动同上REASON_BOOT_AFTER_OTA
BCP APEX 变化同上REASON_BOOT_AFTER_MAINLINE_UPDATE
安装完成performDexoptIfNeededAsyncinstall scenario
后台优化ArtManagerLocal jobbackground
非活跃降级ART Service 策略inactive

本文重点是 PMS 侧的启动和安装入口;具体 compiler filter、profile 和后台 Job 会在后续文章展开。

2. 启动 dexopt 入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java

java
public void performPackageDexOptUpgradeIfNeeded() {
    PackageManagerServiceUtils.enforceSystemOrRoot(
            "Only the system can request package update");
    String reason;
    if (mPm.isFirstBoot()) {
        reason = ReasonMapping.REASON_FIRST_BOOT;
    } else if (mPm.isDeviceUpgrading()) {
        reason = ReasonMapping.REASON_BOOT_AFTER_OTA;
    } else if (hasBcpApexesChanged()) {
        reason = ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE;
    } else {
        return;
    }
    Log.i(TAG, "Starting boot dexopt for reason " + reason);
    getArtManagerLocal().onBoot(
            reason, null /* progressCallbackExecutor */,
            null /* progressCallback */);
}

这是同步启动路径,只允许 system/root 调用。普通启动如果不是首次启动、OTA 或 BCP APEX 变化,直接返回,不重新编译全部应用。

3. BCP APEX 检测 ​

java
private static List<String> getBcpApexes() {
    String bcp = System.getenv("BOOTCLASSPATH");
    if (TextUtils.isEmpty(bcp)) {
        Log.e(TAG, "Unable to get BOOTCLASSPATH");
        return List.of();
    }
    ArrayList<String> bcpApexes = new ArrayList<>();
    for (String pathStr : bcp.split(":")) {
        Path path = Paths.get(pathStr);
        if (path.getNameCount() >= 2
                && path.getName(0).toString().equals("apex")) {
            bcpApexes.add(path.getName(1).toString());
        }
    }
    return bcpApexes;
}

private static boolean hasBcpApexesChanged() {
    Set<String> bcpApexes = new HashSet<>(getBcpApexes());
    for (ActiveApexInfo apexInfo :
            ApexManager.getInstance().getActiveApexInfos()) {
        if (bcpApexes.contains(apexInfo.apexModuleName)
                && apexInfo.activeApexChanged) {
            return true;
        }
    }
    return false;
}

只有位于 BOOTCLASSPATH 的 APEX 变化才触发该 reason。任意 APEX 更新都不等价于 BCP 变化;普通 data APEX 不一定改变应用的编译依赖。

4. 预编译复制 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java

java
public static void requestCopyPreoptedFiles() {
    final String property = "sys.cppreopt";
    if (SystemProperties.getInt(
            "ro.cp_system_other_odex", 0) != 1) {
        return;
    }
    SystemProperties.set(property, "requested");
    final long start = SystemClock.uptimeMillis();
    final long end = start + 100 * 1000;
    long now = start;
    while (!SystemProperties.get(property).equals("finished")) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // continue waiting
        }
        now = SystemClock.uptimeMillis();
        if (now > end) {
            SystemProperties.set(property, "timed-out");
            Slog.wtf(TAG, "cppreopt did not finish!");
            break;
        }
    }
}

PMS 只通过 system property 请求复制,实际文件操作由 init/cppreopt 完成。最多等待 100 秒;超时记录 wtf,但不会在此处执行 dex2oat。

5. 安装 dexopt 调度 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java

java
CompletableFuture<Void> performDexoptIfNeededAsync(
        InstallRequest installRequest) {
    if (!shouldCallArtService(installRequest)) {
        return CompletableFuture.completedFuture(null);
    }
    return CompletableFuture.runAsync(() -> {
        try {
            Trace.traceBegin(
                    TRACE_TAG_PACKAGE_MANAGER, "dexopt");
            DexoptResult result =
                    dexoptPackageUsingArtService(installRequest);
            installRequest.onDexoptFinished(result);
        } finally {
            Trace.traceEnd(
                    TRACE_TAG_PACKAGE_MANAGER);
        }
    }, sDexoptExecutor).exceptionally(t -> {
        Slog.wtf(TAG, "Dexopt encountered a fatal error", t);
        return null;
    });
}

安装 dexopt 在专用 executor 异步执行。DexoptResult 通过 InstallRequest.onDexoptFinished 回传;Future 异常只记录 wtf,普通安装不会因为 dexopt 异常被回滚。

6. ART 调用 ​

java
private DexoptResult dexoptPackageUsingArtService(
        InstallRequest installRequest) {
    final PackageSetting ps =
            installRequest.getScannedPackageSetting();
    try (PackageManagerLocal.FilteredSnapshot snapshot =
            PackageManagerLocalImpl.withFilteredSnapshot(
                    getPackageManagerLocal(), ps)) {
        DexoptParams params =
                getDexoptParamsByInstallRequest(installRequest);
        return getArtManagerLocal().dexoptPackage(
                snapshot, ps.getPackageName(), params);
    }
}

ART Service 使用以目标 PackageSetting 为 owner 的 filtered snapshot,避免把不相关包状态暴露给 ART。PMS 构造 DexoptParams,ART Service 决定具体编译动作和产物。

7. 安装参数 ​

java
private DexoptParams getDexoptParamsByInstallRequest(
        InstallRequest installRequest) {
    String reason = mInstallScenarioHelper
            .getCompilationReasonForInstallScenario(
                    installRequest.getInstallScenario());
    var builder = new DexoptParams.Builder(reason);
    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();
}

设备恢复/设置使用 interactive fast priority;安装参数显式指定 compiler filter 时优先使用该值,否则跳过条件会传 NOOP。INSTALL_IGNORE_DEXOPT_PROFILE 只影响本次安装使用 profile,不影响后续后台 dexopt。

8. 跳过条件 ​

java
private static boolean shouldCallArtService(
        InstallRequest installRequest) {
    final boolean isApex =
            (installRequest.getScanFlags() & SCAN_AS_APEX) != 0;
    final boolean instantApp =
            (installRequest.getScanFlags()
                    & SCAN_AS_INSTANT_APP) != 0;
    final PackageSetting ps =
            installRequest.getScannedPackageSetting();
    final AndroidPackage pkg = ps.getPkg();
    final boolean performDexOptForRollback =
            !(installRequest.isRollback()
            && installRequest.getInstallSource()
                    .mInitiatingPackageName.equals("android"));
    return !instantApp && pkg != null && !isApex
            && performDexOptForRollback;
}

APEX、Instant App、缺少 AndroidPackage 和 system 发起的 rollback 不调用 ART Service。代码注释特别提醒:新增条件通常应放在 shouldSkipDexopt,让 ART Service 仍有机会以 NOOP 参数参与决策。

java
private static boolean shouldSkipDexopt(
        InstallRequest installRequest) {
    PackageSetting ps =
            installRequest.getScannedPackageSetting();
    AndroidPackage pkg = ps.getPkg();
    boolean onIncremental = isIncrementalPath(
            ps.getPathString());
    return pkg == null || pkg.isDebuggable() || onIncremental;
}

debuggable 包和 Incremental FS 包不是“不调用 ART Service”,而是通过 compiler filter NOOP 让 ART Service 参与并跳过编译。这个区别影响 trace、结果回调和后续策略。

9. 结果与安装继续 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallRequest.java

java
public void onDexoptFinished(
        DexoptResult dexoptResult) {
    for (PackageDexoptResult packageResult :
            dexoptResult.getPackageDexoptResults()) {
        if (packageResult.getStatus()
                == DexoptResult.DEXOPT_FAILED) {
            addWarning("Error occurred during dexopt");
        }
    }
    mDexoptStatus = dexoptResult.getFinalStatus();
}

安装请求记录 dexopt 最终状态和 warning。普通 APK 即使 dexopt 失败仍可解释执行;后续后台优化或下一次启动可能再次编译。

10. 调试入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java

java
public static void dumpDexoptState(
        @NonNull IndentingPrintWriter ipw,
        @NonNull PackageDataSnapshot computer,
        @Nullable String packageName) {
    try (PackageManagerLocal.FilteredSnapshot snapshot =
            getPackageManagerLocal()
                    .withUnownedFilteredSnapshot(computer)) {
        if (packageName != null) {
            getArtManagerLocal().dumpPackage(
                    ipw, snapshot, packageName);
        } else {
            getArtManagerLocal().dump(ipw, snapshot);
        }
    }
}

dumpDexoptState 可以针对单包或全部包输出 ART 状态,查询使用 filtered snapshot。包在 dump 期间被删除时,Android 17 对单包路径允许捕获 IllegalArgumentException 并输出异常,而不是让 dumpsys 崩溃。

11. 排查清单 ​

  1. 启动没有 dexopt:确认是否首次启动、OTA 或 BCP APEX 变化。
  2. 安装没有编译:检查 APEX、Instant App、Incremental FS、debuggable 和 system rollback 条件。
  3. dexopt 失败但安装成功:查看 InstallRequest warning 和 DexoptResult 状态。
  4. 恢复/设置安装很慢:检查 interactive fast priority 与 profile 是否被忽略。
  5. 预编译复制超时:查看 sys.cppreopt 和 cppreopt 日志,不要把它误判为 ART Service dexopt。
  6. dump 结果为空:确认 ART Manager 已初始化且 filtered snapshot 中仍存在目标包。

12. 源码阅读路线 ​

  1. DexOptHelper.performPackageDexOptUpgradeIfNeeded:启动触发原因。
  2. getBcpApexes/hasBcpApexesChanged:Mainline 影响范围。
  3. performDexoptIfNeededAsync:安装异步调度与异常语义。
  4. shouldCallArtService/shouldSkipDexopt:调用与 NOOP 的区别。
  5. getDexoptParamsByInstallRequest:reason、priority、filter 和 profile flags。
  6. InstallRequest.onDexoptFinished、dumpDexoptState:结果消费和诊断。

dexopt 在 PMS 中是策略编排层,不是编译器本身:PMS 判断何时需要优化、以什么 reason 调 ART Service、哪些场景应跳过,以及失败后如何继续安装。理解这些边界后,才能把“没有 oat 文件”“安装很慢”和“dexopt 失败但应用仍能运行”分别定位到正确层次。