Skip to content

DexOptHelper 入口

深入分析 DexOptHelper 的 ART 初始化、启动 dexopt、安装调度和状态转储入口。

AndroidPMSDexOptART

DexOptHelper 入口 ​

DexOptHelper 是 PMS 与 ART Service 之间的编排层。它不实现 dex2oat,也不直接决定每个方法的机器码布局,而是负责建立 ArtManagerLocal、选择启动 reason、把安装请求转换为 DexoptParams、决定是否调用 ART,以及把结果和异常交回安装流程。

1. 类的状态 ​

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

java
private static final ThreadPoolExecutor sDexoptExecutor =
        new ThreadPoolExecutor(
                1, 1, 60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());

private static boolean sArtManagerLocalIsInitialized = false;

private final PackageManagerService mPm;
private final InstallScenarioHelper mInstallScenarioHelper;

static {
    sDexoptExecutor.allowsCoreThreadTimeOut();
}

DexOptHelper(PackageManagerService pm) {
    mPm = pm;
    mInstallScenarioHelper =
            new InstallScenarioHelper(mPm.mContext);
}

executor 是单线程、无界队列,安装 dexopt 按提交顺序串行执行;核心线程空闲 60 秒可回收。sArtManagerLocalIsInitialized 只表示 LocalManager 中是否已经注册 ART manager,不表示某个包已经完成编译。

2. ART Manager ​

java
public static void initializeArtManagerLocal(
        @NonNull Context systemContext,
        @NonNull PackageManagerService pm) {
    ArtManagerLocal artManager =
            new ArtManagerLocal(systemContext);
    artManager.addDexoptDoneCallback(
            false /* onlyIncludeUpdates */, Runnable::run,
            pm.getDexOptHelper().new DexoptDoneHandler());
    LocalManagerRegistry.addManager(
            ArtManagerLocal.class, artManager);
    sArtManagerLocalIsInitialized = true;

PMS 创建 ART manager 后立即注册 dexopt 完成回调,再写入 LocalManagerRegistry。回调使用 Runnable::run,由 ART manager 的调用线程直接执行 PMS 的完成处理。

java
    systemContext.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.unregisterReceiver(this);
            artManager.scheduleBackgroundDexoptJob();
        }
    }, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
    StagedApexObserver.registerForStagedApexUpdates(artManager);
}

后台 dexopt Job 不在初始化时立即调度,而是等 LOCKED_BOOT_COMPLETED 后调度;StagedApexObserver 则从初始化阶段开始监听 staged APEX 更新。初始化顺序错误时,getArtManagerLocal() 会抛 RuntimeException,这能暴露启动依赖问题。

3. 启动 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, null);
}

只有 system/root 能触发启动级 dexopt。普通重启直接返回;首次启动、OTA 和 BCP APEX 变化分别映射到 ART 的不同 reason,ART Service 再据此决定范围和编译策略。

4. 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> result = new ArrayList<>();
    for (String pathStr : bcp.split(":")) {
        Path path = Paths.get(pathStr);
        if (path.getNameCount() >= 2
                && path.getName(0).toString().equals("apex")) {
            result.add(path.getName(1).toString());
        }
    }
    return result;
}

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

检测只关心 BOOTCLASSPATH 中的 APEX module。APEX 更新但不参与 boot classpath 时,不会触发这个启动 reason;路径解析失败返回空列表并记录错误。

5. 安装参数组装 ​

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

安装场景映射 compilation reason;设备恢复/设置提高 priority;显式 compiler filter 优先级最高;跳过条件通过 NOOP 交给 ART。INSTALL_IGNORE_DEXOPT_PROFILE 只影响本次安装,不会禁用未来的后台优化。

6. 调用与跳过 ​

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 rollback =
            !(installRequest.isRollback()
            && installRequest.getInstallSource()
                    .mInitiatingPackageName.equals("android"));
    return !instantApp && pkg != null && !isApex && rollback;
}

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

shouldCallArtService=false 意味着直接返回 completed Future;shouldSkipDexopt=true 仍会调用 ART Service,但带 COMPILER_FILTER_NOOP。Android 17 明确将这两类条件分开,便于 ART 统一记录和决定后续行为。

7. 安装异步执行 ​

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 失败应表现为 DexoptResult.DEXOPT_FAILED;只有未预期异常才进入 exceptionally。两者都不把普通应用安装改成失败,安装流程可以继续使用解释执行。

8. 完成回调 ​

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

java
private class DexoptDoneHandler
        implements ArtManagerLocal.DexoptDoneCallback {
    @Override
    public void onDexoptDone(@NonNull DexoptResult result) {
        var updatedPackages = new ArraySet<String>();
        for (DexoptResult.PackageDexoptResult pkgRes :
                result.getPackageDexoptResults()) {
            if (pkgRes.hasUpdatedArtifacts()) {
                updatedPackages.add(pkgRes.getPackageName());
            }
        }
        if (!updatedPackages.isEmpty()) {
            LocalServices.getService(PinnerService.class)
                    .update(updatedPackages, false /* force */);
        }
    }
}

ART Service 报告产生新 artifacts 的包后,DexOptHelper 通知 PinnerService 更新内存锁定集合。dexopt 产物更新和 pinner 更新是两个消费者,前者由 ART 负责,后者由 PMS 回调串联。

9. 状态转储 ​

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

dump 使用 filtered snapshot,单包查询遇到并发删除时把 IllegalArgumentException 打印出来;全量查询直接让 ART manager 输出所有包状态。dump 反映的是 ART 当前状态,不等同于“本次安装是否调用过 dexopt”。

10. 排查顺序 ​

  1. 启动 dexopt 没发生:检查首次启动、OTA、BCP APEX 变化和 system/root caller。
  2. 安装未调用 ART:检查 APEX、Instant App、包对象为空和 system rollback。
  3. ART 被调用但无编译产物:检查 shouldSkipDexopt、NOOP filter、Incremental FS 和 compiler filter。
  4. 安装成功但 dexopt warning:查看 DexoptResult 和 InstallRequest.onDexoptFinished。
  5. 后台 Job 未出现:确认 initializeArtManagerLocal 注册的 LOCKED_BOOT_COMPLETED 回调已触发。
  6. dump 为空或报包不存在:检查 filtered snapshot 和安装/卸载并发时序。

11. 源码阅读路线 ​

  1. PackageManagerService 构造和 initializeArtManagerLocal:确认 ART manager 注册时机。
  2. performPackageDexOptUpgradeIfNeeded:启动 reason 选择。
  3. getBcpApexes/hasBcpApexesChanged:BCP 依赖检测。
  4. getDexoptParamsByInstallRequest:安装 reason、priority、filter 和 profile flags。
  5. shouldCallArtService/shouldSkipDexopt:跳过调用与 NOOP 的区别。
  6. performDexoptIfNeededAsync/dexoptPackageUsingArtService:异步执行和 filtered snapshot。
  7. DexoptDoneHandler/dumpDexoptState:结果消费者和诊断入口。

DexOptHelper 的核心价值是把 PMS 的包生命周期转换成 ART Service 可以消费的 reason、参数和快照,并在编译成功、失败、跳过和初始化异常之间保持清晰边界。理解这些入口后,才能继续阅读 PackageDexOptimizer 和 compiler filter 的细节。