Skip to content

安装时 dexopt

追踪安装流程中路径重命名、应用数据准备、ART Service 调度、编译参数和 dexopt 收尾。

基于android-17.0.0_r1
AndroidPackageManagerServiceInstallPackageHelperDexOptART源码阅读

安装时 dexopt ​

本文承接 APK 拷贝到 data-app、InstallPackageHelper 入口 和 安装前检查。主题是安装主线中的 dexopt 插入点:为什么必须先确定最终 code path,谁准备 app data 和 profile,哪些包会跳过 ART Service,失败结果如何影响 commit。

本文不展开 ART Service 的启动 dexopt、后台 job 和全量编译策略;这里只讨论 InstallPackageHelper 如何为一批 ReconciledPackage 准备 dexopt,并把 InstallRequest 交给 DexOptHelper。

1. 安装主线中的位置 ​

1.1 Dexopt 位置 ​

InstallPackageHelper.installPackagesTraced() 在 rename 成功后设置 pendingForDexopt=true,把后续流程交给 dexopt callback。这样顶层 finally 不会提前完成安装、释放 WakeLock 或执行 post-install。

1.2 两个成功状态 ​

状态owner含义
InstallRequest.returnCode安装流程prepare/copy/scan/reconcile/commit 的安装结果
InstallRequest.mDexoptStatusdexopt metricsART 返回的 performed/skipped/failed 状态

Android 17 的普通 dexopt 失败通常写入 mDexoptStatus 和 metrics,不把 returnCode 改成安装失败;因此“dexopt failed”不必然等于“APK 安装失败”。

2. 前置条件 ​

2.1 路径前置 ​

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

java
if (reconciledPackages == null) {
    return;
}
if (renameAndUpdatePaths(requests)) {
    // rename before dexopt because art will encode the path in odex/vdex
    pendingForDexopt = true;
    final Runnable actionsAfterDexopt = () -> doPostDexopt(
            reconciledPackages, requests, createdAppId, moveInfo, acquireTime);
    prepPerformDexoptIfNeeded(reconciledPackages, actionsAfterDexopt);
}

ART 生成的 odex/vdex 会绑定 code path,所以 rename 必须在 dexopt 前完成。此时 ParsedPackage、InstallRequest.codeFile 和 PackageSetting.path 已经指向最终路径,ART 看到的是安装后的路径而不是临时 stage。

2.2 Data/profile ​

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

java
final PackageSetting ps = request.getScannedPackageSetting();
final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
final int[] allUsers = mPm.mUserManager.getUserIds();
if (reconciledPkg.mCollectedSharedLibraryInfos != null
        || (oldPkgSetting != null
        && !oldPkgSetting.getSharedLibraryDependencies().isEmpty())) {
    mSharedLibraries.executeSharedLibrariesUpdate(
            request.getParsedPackage(), ps, null, null,
            reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
}

try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
    final int[] newUsers = getNewUsers(request, allUsers);
    // Disable data migration for this post-commit preparation.
    mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, newUsers);
    if (request.isClearCodeCache()) {
        mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
                FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                        | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
    }
} catch (PackageManagerException e) {
    request.setError(e.error, e.getMessage());
    completableFutures.clear();
    break;
}
request.setKeepArtProfile(true);

在调用 ART 前,InstallPackageHelper 先更新共享库关系、准备 app data,并按 request 清理旧 code cache。setKeepArtProfile(true) 告诉后续收尾保留 profile;这些操作失败会清空 future 列表并停止准备循环。

3. Dexopt 调度 ​

3.1 批量 future ​

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

java
private void prepPerformDexoptIfNeeded(
        List<ReconciledPackage> reconciledPackages, Runnable actionsAfterDexopt) {
    List<CompletableFuture<Void>> completableFutures = new ArrayList<>();
    for (ReconciledPackage reconciledPkg : reconciledPackages) {
        final InstallRequest request = reconciledPkg.mInstallRequest;
        request.setKeepArtProfile(true);
        CompletableFuture<Void> future =
                mPm.getDexOptHelper().performDexoptIfNeededAsync(request);
        completableFutures.add(future);
        request.onWaitDexoptStarted();
    }

    if (!completableFutures.isEmpty()) {
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
                completableFutures.toArray(CompletableFuture[]::new));
        allFutures.thenRun(() -> mPm.mHandler.post(actionsAfterDexopt));
    } else {
        actionsAfterDexopt.run();
    }
}

每个 request 都得到一个 future;批量安装用 CompletableFuture.allOf() 等待全部任务。若所有 request 都被判定为不需要调用 ART Service,future 会立即完成,流程直接进入 callback。

3.2 Request 计时 ​

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

java
public void onWaitDexoptStarted() {
    if (mPackageMetrics != null) {
        mPackageMetrics.onStepStarted(PackageMetrics.STEP_WAIT_DEXOPT);
    }
}

public void onWaitDexoptFinished() {
    if (mPackageMetrics != null) {
        mPackageMetrics.onStepFinished(PackageMetrics.STEP_WAIT_DEXOPT);
    }
}

public void onDexoptFinished(DexoptResult dexoptResult) {
    if (mPackageMetrics != null) {
        mDexoptStatus = dexoptResult.getFinalStatus();
        if (mDexoptStatus == DexoptResult.DEXOPT_PERFORMED) {
            mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT,
                    /*durationMillis=*/ 0);
        }
    }
}

onWaitDexoptStarted/Finished() 记录等待阶段,onDexoptFinished() 保存 ART 最终状态并更新 metrics。源码没有在这个方法中调用 setReturnCode(),这正是普通 dexopt 失败不会直接转为安装失败的证据。

4. ART Service ​

4.1 是否调用 ART ​

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

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;
}

Instant App、APEX、没有 package 对象的 request,以及由 Android 发起的 rollback 不调用 ART Service。这里是“是否进入 ART”的硬过滤;进入后 ART 仍可能根据 compiler filter 选择不编译。

4.2 是否跳过编译 ​

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 path 仍可能调用 ART Service,但会通过 NOOP compiler filter 请求 ART 自己跳过或决定。不要把 shouldSkipDexopt() 与 shouldCallArtService() 混为同一个条件。

5. Dexopt 参数 ​

5.1 Reason 与优先级 ​

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

java
private DexoptParams getDexoptParamsByInstallRequest(
        InstallRequest installRequest) {
    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);
    }

编译 reason 来自 install scenario;设备恢复或设备设置场景使用 interactive-fast 优先级。安装 reason 和 install scenario 是两个字段,不能只读取其中一个。

5.2 Filter/profile ​

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

java
    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();
}

显式 compiler filter 优先;否则对 debuggable/incremental 等场景使用 NOOP。INSTALL_IGNORE_DEXOPT_PROFILE 通过 ART flag 忽略 profile,而不是删除 profile 文件。

5.3 快照与 ART ​

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

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

ART 使用针对 package 的 FilteredSnapshot,而不是直接暴露 PMS 全局对象。快照关闭后返回 DexoptResult,InstallRequest 只保存状态和 metrics。

6. 异步执行与失败 ​

6.1 单线程 executor ​

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

java
@NonNull
private static final ThreadPoolExecutor sDexoptExecutor =
        new ThreadPoolExecutor(1 /*corePoolSize*/, 1 /*maximumPoolSize*/,
                60 /*keepAliveTime*/, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());

安装 dexopt 使用单线程 executor,避免同时运行多个 ART Service 编译任务;core thread 允许超时回收。

6.2 Future 与异常 ​

源码文件: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");
            // Don't fail application installs if the dexopt step fails.
            DexoptResult dexOptResult = dexoptPackageUsingArtService(installRequest);
            installRequest.onDexoptFinished(dexOptResult);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }, sDexoptExecutor).exceptionally((t) -> {
        // Normal dexopt failure should be represented by DexoptResult, not an exception.
        Slog.wtf(TAG, "Dexopt encountered a fatal error", t);
        return null;
    });
}

普通 dexopt 失败预期由 DexoptResult 表示;future 异常被 exceptionally 记录并转成完成状态。这样 callback 仍能进入 doPostDexopt()。

6.3 Commit 判断 ​

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

java
void doPostDexopt(List<ReconciledPackage> reconciledPackages,
        List<InstallRequest> requests, Map<String, Boolean> createdAppId,
        MoveInfo moveInfo, long acquireTime) {
    boolean isDexoptCompleted = true;
    for (InstallRequest request : requests) {
        request.onWaitDexoptFinished();
        if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
            isDexoptCompleted = false;
        }
    }
    boolean success = false;
    try {
        if (isDexoptCompleted && commitInstallPackages(reconciledPackages)) {
            success = true;
        }
    } finally {
        completeInstallProcess(requests, createdAppId, success);
        doPostInstall(requests, moveInfo);
        releaseWakeLock(acquireTime, requests.size());
    }
}

这里检查的是 request.getReturnCode(),不是 getDexoptStatus()。因此 dexopt 的普通 failed status 不会自动阻断 commit;只有 request 在更早阶段已经失败,或 commit 自身失败,才会让安装整体失败。

7. Profile 与共享库 ​

7.1 共享库先更新 ​

prepPerformDexoptIfNeeded() 在 app data 前先检查新旧包的共享库依赖,并调用 executeSharedLibrariesUpdate()。ART 的 class path 需要与安装后共享库状态一致,不能在旧 library graph 上编译。

7.2 保留 profile ​

request.setKeepArtProfile(true) 并不等于立即加载 profile。它把保留策略写入 InstallRequest,后续清理逻辑依据该状态决定是否保留 ART profile;INSTALL_IGNORE_DEXOPT_PROFILE 只影响 ART 参数。

7.3 app data 失败 ​

app data 准备失败会调用 request.setError(e.error, e.getMessage()),清空已收集 futures 并停止循环。由于此时部分 request 可能已有 future,诊断时应同时检查 request return code 和 future 数量。

8. 锁、冻结与清理 ​

8.1 锁与阶段 ​

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

rename 和 app data 准备使用 mInstallLock;ART 在单独 executor 中运行;callback 回到 PMS handler,再进入冻结和 commit。锁不跨越整个 dexopt 等待期。

8.2 commit 前冻结 ​

同步 commit 会在 mInstallLock 下创建 PackageFreezer,再在 mPm.mLock 下调用 commitPackagesLocked()。启用 app restart after update 时,冻结等待可以异步并行,但 commit 仍在 PMS 锁下执行。

8.3 失败收尾 ​

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

java
if (!success) {
    for (InstallRequest installRequest : requests) {
        if (installRequest.getParsedPackage() != null
                && createdAppId.getOrDefault(
                        installRequest.getParsedPackage().getPackageName(), false)) {
            cleanUpAppIdCreations(installRequest);
        }
    }
    for (InstallRequest request : requests) {
        request.closeFreezer();
        if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
            request.setReturnCode(PackageManager.INSTALL_UNKNOWN);
        }
    }
}

失败时释放本次创建的 appId、关闭 freezer,并把尚未明确失败的 request 标记为 UNKNOWN,避免把部分完成的批量安装当成成功。

9. 安装时序 ​

9.1 完整路径 ​

9.2 判断表 ​

条件是否调用 ART是否阻断 commit
Instant App否否,仅因 dexopt 而言
APEX否否,仅因 dexopt 而言
debuggable/incremental是,但可使用 NOOP filter否
ART 返回 DEXOPT_FAILED是,记录状态通常否
request 先前已有错误future 可完成是
commitPackagesLocked 失败已调用 commit是,进入失败收尾

10. 测试与诊断 ​

10.1 输入与断言 ​

输入断言证明范围
Instant/APEX requestshouldCallArtService=false硬跳过条件
debuggable packagecompiler filter 为 NOOP 或 ART 自行跳过soft skip
device restore/setuppriority interactive-fast参数构造
explicit compiler filter覆盖 skip 默认值参数优先级
ignore profile flag设置 FLAG_IGNORE_PROFILEprofile 策略
app data failurerequest error,future 列表停止追加前置失败
DexoptResult failedmDexoptStatus 更新但 returnCode 不变失败语义
request pre-faileddoPostDexopt 不 commit阶段短路
rename before dexoptART 使用最终 code path路径时机

测试必须同时断言 InstallRequest.returnCode 和 getDexoptStatus();只检查其中一个会把“编译失败但安装成功”误判成“安装失败”。

10.2 现场排查顺序 ​

  1. 确认 renameAndUpdatePaths() 是否已经完成,检查 parsed/base/split/native paths。
  2. 检查 app data/profile 准备是否成功,是否因 clearCodeCache 清理旧缓存。
  3. 查看 shouldCallArtService() 的 instant/APEX/rollback 条件。
  4. 查看 DexoptParams 的 reason、priority、compiler filter 和 profile flag。
  5. 区分 ART 返回的 DexoptResult 状态与 request return code。
  6. 检查 future 是否全部完成,以及 doPostDexopt() 是否回到 PMS handler。
  7. 若 commit 未执行,优先查 request 早期错误;若 commit 后失败,再查 freezer、appId 和 code path 清理。

11. 源码路线 ​

建议按以下顺序阅读:

  1. InstallPackageHelper.installPackagesTraced():确认 rename 与 dexopt 的整体位置。
  2. prepPerformDexoptIfNeeded():看共享库、app data、profile 和 future。
  3. DexOptHelper.shouldCallArtService() / shouldSkipDexopt():区分硬跳过和 NOOP filter。
  4. getDexoptParamsByInstallRequest():追踪 reason、priority、compiler filter 和 flags。
  5. performDexoptIfNeededAsync():理解单线程 executor 和异常策略。
  6. dexoptPackageUsingArtService():查看 FilteredSnapshot 与 ART 调用。
  7. InstallRequest.onDexoptFinished():确认 dexopt status 与安装 return code 分离。
  8. doPostDexopt():确认 commit 是否由 request 状态决定。

12. 设计收束 ​

安装时 dexopt 的真实链路是:

text
reconcile
  -> 最终 code path
  -> app data/profile 准备
  -> shouldCallArtService
  -> DexoptParams + FilteredSnapshot
  -> 单线程 ART executor
  -> DexoptResult/metrics
  -> 依据 InstallRequest.returnCode 决定 commit

最容易误读的地方有三个:rename 必须早于 dexopt;shouldSkipDexopt 不等于不调用 ART;DexoptResult 的 failed status 不会自动改写安装 return code。把路径 owner、ART 状态和 InstallRequest 状态分开,才能解释为什么某个 APK 没有 odex、为什么 debuggable/incremental 包仍进入 ART,以及为什么日志显示 dexopt failed 时安装仍可能成功。