安装时 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.mDexoptStatus | dexopt metrics | ART 返回的 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
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
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
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
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
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 是否跳过编译
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
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
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
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
@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
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
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
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 request | shouldCallArtService=false | 硬跳过条件 |
| debuggable package | compiler filter 为 NOOP 或 ART 自行跳过 | soft skip |
| device restore/setup | priority interactive-fast | 参数构造 |
| explicit compiler filter | 覆盖 skip 默认值 | 参数优先级 |
| ignore profile flag | 设置 FLAG_IGNORE_PROFILE | profile 策略 |
| app data failure | request error,future 列表停止追加 | 前置失败 |
| DexoptResult failed | mDexoptStatus 更新但 returnCode 不变 | 失败语义 |
| request pre-failed | doPostDexopt 不 commit | 阶段短路 |
| rename before dexopt | ART 使用最终 code path | 路径时机 |
测试必须同时断言 InstallRequest.returnCode 和 getDexoptStatus();只检查其中一个会把“编译失败但安装成功”误判成“安装失败”。
10.2 现场排查顺序
- 确认
renameAndUpdatePaths()是否已经完成,检查 parsed/base/split/native paths。 - 检查 app data/profile 准备是否成功,是否因
clearCodeCache清理旧缓存。 - 查看
shouldCallArtService()的 instant/APEX/rollback 条件。 - 查看
DexoptParams的 reason、priority、compiler filter 和 profile flag。 - 区分 ART 返回的
DexoptResult状态与 request return code。 - 检查 future 是否全部完成,以及
doPostDexopt()是否回到 PMS handler。 - 若 commit 未执行,优先查 request 早期错误;若 commit 后失败,再查 freezer、appId 和 code path 清理。
11. 源码路线
建议按以下顺序阅读:
InstallPackageHelper.installPackagesTraced():确认 rename 与 dexopt 的整体位置。prepPerformDexoptIfNeeded():看共享库、app data、profile 和 future。DexOptHelper.shouldCallArtService()/shouldSkipDexopt():区分硬跳过和 NOOP filter。getDexoptParamsByInstallRequest():追踪 reason、priority、compiler filter 和 flags。performDexoptIfNeededAsync():理解单线程 executor 和异常策略。dexoptPackageUsingArtService():查看 FilteredSnapshot 与 ART 调用。InstallRequest.onDexoptFinished():确认 dexopt status 与安装 return code 分离。doPostDexopt():确认 commit 是否由 request 状态决定。
12. 设计收束
安装时 dexopt 的真实链路是:
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 时安装仍可能成功。
