Staged APEX
本文承接 APEX 安装更新,进一步进入 StagingManager。PMS135 解释了 VERIFIED → STAGED → ACTIVATED;本文回答重启后 framework 如何把 PackageInstaller session 与 apexd session 对齐、如何处理 APEX+APK 混合会话,以及 activation 失败时为什么可能触发 checkpoint/reboot。
StagingManager 不验证 APEX payload,也不挂载模块。它管理 framework 的 StagedSession 状态,查询 apexd 的 ApexSessionInfo,在重启后决定继续安装 APK、标记失败、abort dangling session 或回滚文件系统。
1. 状态所有者
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:核心字段
private final ApexManager mApexManager;
private final PowerManager mPowerManager;
private final Context mContext;
private final File mFailureReasonFile =
new File(
"/metadata/staged-install/failure_reason.txt");
@GuardedBy("mStagedSessions")
private final SparseArray<StagedSession>
mStagedSessions = new SparseArray<>();
@GuardedBy("mSuccessfulStagedSessionIds")
private final List<Integer>
mSuccessfulStagedSessionIds = new ArrayList<>();
private final CompletableFuture<Void>
mBootCompleted = new CompletableFuture<>();framework session、apexd session 和 filesystem checkpoint 是三个状态源。mFailureReasonFile 放在 /metadata,避免 /data checkpoint 回滚时丢失失败原因;成功 session ID 列表则等待 boot-completed 后再确认。
2. Session 契约
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:StagedSession
interface StagedSession {
boolean isMultiPackage();
boolean isApexSession();
boolean isCommitted();
boolean isInTerminalState();
boolean isDestroyed();
boolean isSessionReady();
boolean isSessionApplied();
boolean isSessionFailed();
List<StagedSession> getChildSessions();
boolean containsApkSession();
boolean containsApexSession();
void setSessionReady();
void setSessionFailed(int errorCode,
String errorMessage);
void setSessionApplied();
CompletableFuture<Void> installSession();
void abandon();
void verifySession();
}PackageInstallerSession.StagedSession 实现这个接口,使 StagingManager 不直接依赖整个安装 session。接口同时表达容器结构、生命周期状态和操作能力,特别支持父 session 中混合 APEX/APK 子 session。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java,符号:StagedSession.installSession
public CompletableFuture<Void> installSession() {
assertCallerIsOwnerOrRootOrSystem();
assertNotChild("StagedSession#installSession");
Preconditions.checkArgument(
isCommitted() && isSessionReady());
return install();
}重启后的 APK 安装只能从 ready 的顶层 session 开始;子 session 由父 session 原子协调,不能单独调用。
3. 提交到内存状态
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:commitSession
void commitSession(@NonNull StagedSession session) {
createSession(session);
handleCommittedSession(session);
}
void createSession(@NonNull StagedSession session) {
synchronized (mStagedSessions) {
mStagedSessions.append(
session.sessionId(), session);
}
}
private void handleCommittedSession(
@NonNull StagedSession session) {
if (session.isSessionReady()
&& session.containsApexSession()) {
notifyStagedApexObservers();
}
}commit 先把顶层 session 放入 mStagedSessions;ready 且包含 APEX 时才通知观察者。观察者看到的是 staged APEX 信息变化,不是 activation 完成。
4. 重启恢复入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:restoreSessions
void restoreSessions(
@NonNull List<StagedSession> sessions,
boolean isDeviceUpgrading) {
if (SystemProperties.getBoolean(
"sys.boot_completed", false)) {
return;
}
for (StagedSession session : sessions) {
Preconditions.checkArgument(
!session.hasParentSessionId());
Preconditions.checkArgument(
session.isCommitted());
Preconditions.checkArgument(
!session.isInTerminalState());
createSession(session);
}
if (isDeviceUpgrading) {
for (StagedSession session : sessions) {
session.setSessionFailed(
PackageManager.INSTALL_ACTIVATION_FAILED,
"Build fingerprint has changed");
}
return;
}恢复只能在 boot completed 前执行,只接受 committed、非终态、顶层 session。设备发生另外一次 OTA、build fingerprint 改变时,预重启验证条件已不可靠,所有旧 staged session 直接失败。
5. 未完成会话
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:handleNonReadyAndDestroyedSessions
if (session.isDestroyed()) {
session.abandon();
removeFromResumeList(session);
} else if (!session.isSessionReady()) {
Slog.i(TAG,
"Restart verification for session="
+ session.sessionId());
mBootCompleted.thenRun(
() -> session.verifySession());
removeFromResumeList(session);
}framework 在 pre-reboot verification 完成前崩溃时,非-ready session 不在本次启动直接安装,而是等待 boot completed 后重新验证;destroyed session 则继续 abandon 清理。这样避免开机关键路径重复执行耗时验证。
6. apexd 状态对齐
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:restoreSessions 的状态检查
SparseArray<ApexSessionInfo> apexSessions =
mApexManager.getSessions();
for (StagedSession session : sessions) {
if (!session.containsApexSession()) {
continue;
}
ApexSessionInfo apexSession =
apexSessions.get(session.sessionId());
if (apexSession == null || apexSession.isUnknown) {
session.setSessionFailed(
PackageManager.INSTALL_ACTIVATION_FAILED,
"apexd did not know staged session");
} else if (isApexSessionFailed(apexSession)) {
session.setSessionFailed(
PackageManager.INSTALL_ACTIVATION_FAILED,
"APEX activation failed");
} else if (apexSession.isActivated
|| apexSession.isSuccess) {
hasAppliedApexSession = true;
} else if (apexSession.isStaged) {
session.setSessionFailed(
PackageManager.INSTALL_ACTIVATION_FAILED,
"Session did not activate at boot");
}
}重启后仍停留在 STAGED 也被视为失败,因为不能保证下次启动会自动激活。apexd 不知道 session、activation failed/reverted 或 impossible state 都会同步为 framework session failed。
7. 混合成功与失败
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:全局一致性检查
if (hasAppliedApexSession
&& hasFailedApexSession) {
abortCheckpoint(
"Found both applied and failed apex sessions",
supportsCheckpoint, needsCheckpoint);
return;
}
if (hasFailedApexSession) {
for (StagedSession session : sessions) {
if (!session.isSessionFailed()) {
session.setSessionFailed(
PackageManager.INSTALL_ACTIVATION_FAILED,
"Another apex session failed");
}
}
for (int id : sessionIdsToBeAborted) {
mApexManager.abortStagedSession(id);
}
return;
}多个 staged APEX 不能一部分 applied、一部分 failed 后继续运行,否则系统组件版本组合不可预测。checkpoint 可用时会整体回滚;任一 APEX 失败还会把其他 session 标为失败,维护跨 session 一致性。
8. resumeSession
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:resumeSession
if (supportsCheckpoint && !needsCheckpoint) {
session.setSessionFailed(
PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"Reverting back to safe state");
return;
}
if (session.containsApexSession()) {
checkInstallationOfApkInApexSuccessful(session);
checkDuplicateApkInApex(session);
snapshotAndRestoreForApexSession(session);
}
installApksInSession(session);
if (session.containsApexSession()) {
if (supportsCheckpoint) {
synchronized (mSuccessfulStagedSessionIds) {
mSuccessfulStagedSessionIds.add(
session.sessionId());
}
} else {
mApexManager.markStagedSessionSuccessful(
session.sessionId());
}
}APEX 已激活后,framework 还要检查 APK-in-APEX、重复 APK、snapshot/restore,然后安装混合 session 中的普通 APK。checkpoint 设备不能立即标记 successful,而要等 boot completed;无 checkpoint 的设备则立即确认。
9. 安装失败与回滚
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:onInstallationFailure
void onInstallationFailure(StagedSession session,
PackageManagerException e,
boolean supportsCheckpoint,
boolean needsCheckpoint) {
session.setSessionFailed(e.error, e.getMessage());
abortCheckpoint(
"Failed to install sessionId: "
+ session.sessionId(),
supportsCheckpoint, needsCheckpoint);
if (!session.containsApexSession()) {
return;
}
if (mApexManager.revertActiveSessions()) {
mPowerManager.reboot(null);
}
}APK 子会话安装失败也可能使整个 APEX mixed session 回滚。若没有 checkpoint,StagingManager 请求 apexd revert active sessions,并重启回到旧 APEX 组合。失败不是简单删除 stageDir。
10. Abandon 清理
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:abortCommittedSession
if (session.isInTerminalState()) {
return;
}
if (!session.isDestroyed()) {
throw new IllegalStateException(
"Committed session must be destroyed before aborting");
}
if (!ensureActiveApexSessionIsAborted(session)) {
Slog.e(TAG,
"Failed to abort apex session "
+ session.sessionId());
}
abortSession(session);已提交 session 必须先 destroyed 才能从 StagingManager 移除;还要检查 apexd session 是否已经 final,否则调用 abortStagedSession。即使 apexd abort 失败,删除 stageDir 也能防止包在下次启动被激活,但日志必须保留不一致风险。
11. Boot 屏障
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:systemReady、onBootCompletedBroadcastReceived
void systemReady() {
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(
Context ctx, Intent intent) {
onBootCompletedBroadcastReceived();
ctx.unregisterReceiver(this);
}
},
new IntentFilter(
Intent.ACTION_BOOT_COMPLETED));
mFailureReasonFile.delete();
}
void onBootCompletedBroadcastReceived() {
mBootCompleted.complete(null);
BackgroundThread.getExecutor().execute(
this::logFailedApexSessionsIfNecessary);
}boot completed 解锁两个动作:重新启动未完成的 pre-reboot verification,以及确认/记录 staged APEX 结果。checkpoint 模式下 successful session ID 也在这个阶段交给 ApexManager 确认,避免系统尚未稳定就删除回滚备份。
12. 失败定位
| 现象 | 检查点 |
|---|---|
| session 重启后消失 | 顶层/committed/terminal precondition、destroyed 状态 |
| APEX 仍是 STAGED | apexd 未在 boot 激活,framework 会标 activation failed |
| APEX active 但 APK 子包未装 | installApksInSession、APK-in-APEX 检查 |
| 一部分 APEX 成功一部分失败 | checkpoint 整体回滚路径 |
| abandon 后仍被 apexd 记录 | ensureActiveApexSessionIsAborted、dangling session |
| 标记成功过早 | mSuccessfulStagedSessionIds 与 boot-completed 屏障 |
13. 阅读检查
复述重启恢复链:加载 framework staged sessions → 查询 apexd sessions → 清理 destroyed/non-ready/dangling → 判断 activated/failed 一致性 → resumeSession → 安装 APK 子会话 → boot completed 后 markStagedSessionSuccessful。
然后回答:为什么重启后仍为 STAGED 会被判失败?为什么 mixed session 中 APK 失败可以回滚已激活 APEX?为什么 checkpoint 设备延迟 successful?三个答案分别对应激活确定性、原子版本组合和保留回滚窗口。
