dexopt 流程总览
PMS 不直接执行 dex2oat 命令。Android 17 由 DexOptHelper 选择触发原因和安装参数,再通过 ArtManagerLocal 调用 ART Service;安装场景使用异步 Future,启动场景使用同步 boot dexopt。dexopt 失败通常不会使普通 APK 安装失败,而是记录结果并允许运行时解释执行。
1. 触发场景
| 场景 | 入口 | reason |
|---|---|---|
| 首次启动 | performPackageDexOptUpgradeIfNeeded | REASON_FIRST_BOOT |
| OTA 后启动 | 同上 | REASON_BOOT_AFTER_OTA |
| BCP APEX 变化 | 同上 | REASON_BOOT_AFTER_MAINLINE_UPDATE |
| 安装完成 | performDexoptIfNeededAsync | install scenario |
| 后台优化 | ArtManagerLocal job | background |
| 非活跃降级 | ART Service 策略 | inactive |
本文重点是 PMS 侧的启动和安装入口;具体 compiler filter、profile 和后台 Job 会在后续文章展开。
2. 启动 dexopt 入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.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 检测
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
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
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 调用
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. 安装参数
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. 跳过条件
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 参数参与决策。
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
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
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. 排查清单
- 启动没有 dexopt:确认是否首次启动、OTA 或 BCP APEX 变化。
- 安装没有编译:检查 APEX、Instant App、Incremental FS、debuggable 和 system rollback 条件。
- dexopt 失败但安装成功:查看
InstallRequestwarning 和DexoptResult状态。 - 恢复/设置安装很慢:检查 interactive fast priority 与 profile 是否被忽略。
- 预编译复制超时:查看
sys.cppreopt和 cppreopt 日志,不要把它误判为 ART Service dexopt。 - dump 结果为空:确认 ART Manager 已初始化且 filtered snapshot 中仍存在目标包。
12. 源码阅读路线
DexOptHelper.performPackageDexOptUpgradeIfNeeded:启动触发原因。getBcpApexes/hasBcpApexesChanged:Mainline 影响范围。performDexoptIfNeededAsync:安装异步调度与异常语义。shouldCallArtService/shouldSkipDexopt:调用与 NOOP 的区别。getDexoptParamsByInstallRequest:reason、priority、filter 和 profile flags。InstallRequest.onDexoptFinished、dumpDexoptState:结果消费和诊断。
dexopt 在 PMS 中是策略编排层,不是编译器本身:PMS 判断何时需要优化、以什么 reason 调 ART Service、哪些场景应跳过,以及失败后如何继续安装。理解这些边界后,才能把“没有 oat 文件”“安装很慢”和“dexopt 失败但应用仍能运行”分别定位到正确层次。
