StagedSession 暂存安装
本文承接 Session 安装、InstallingSession 会话 和 安装后注册。普通 session 在验证完成后会直接创建 InstallingSession 并进入 APK 安装;staged session 则把“预重启验证”和“重启后激活”分开,由 StagingManager 维护跨重启状态。
本文重点讲 Android 17 的 staged 主线:PackageInstallerSession 如何把已验证 session 交给 StagingManager,PMS 如何在启动时恢复 committed session,APEX 激活状态如何与 APK 安装结果协调,以及 checkpoint 不一致时为什么会把整批 session 标记失败。
1. 两阶段主线
1.1 预重启与重启后
staged 的“成功”在重启前只是 ready,表示可以等待激活;真正 applied 要等重启后的 APK/APEX 处理完成。客户端在 pre-reboot 阶段收到的成功状态不能当作应用已经可运行。
1.2 三个 owner
| owner | 负责状态 |
|---|---|
PackageInstallerSession | sealed、committed、ready/applied/failed、status receiver |
StagingManager | staged session 集合、重启恢复、APEX/checkpoint 协调 |
ApexManager/apeXd | APEX staged、activated、successful、reverted |
2. Session 进入暂存
2.1 验证完成分叉
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
@WorkerThread
private void onVerificationComplete() {
if (isStaged()) {
mStagingManager.commitSession(mStagedSession);
sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged",
/*extras=*/ null, /*forPreapproval=*/ false);
return;
}
if (!Flags.sdkDependencyInstallerDeprecation()
&& params.isAutoInstallDependenciesEnabled
&& !isMultiPackage()) {
mDependencyInstallerEnabled.set(true);
resolveLibraryDependenciesIfNeeded();
} else {
install();
}
}普通 session 走 install();staged session 只提交到 StagingManager,然后向远端 receiver 报告 “Session staged”。依赖安装器和普通 APK 安装不会在这个分支中立即执行。
2.2 暂存适配器
final StagingManager.StagedSession mStagedSession;
public void setSessionReady() {
PackageInstallerSession.this.setSessionReady();
}
public void setSessionFailed(int errorCode, String errorMessage) {
PackageInstallerSession.this.setSessionFailed(errorCode, errorMessage);
}
public void setSessionApplied() {
PackageInstallerSession.this.setSessionApplied();
}
public CompletableFuture<Void> installSession() {
return PackageInstallerSession.this.install();
}PackageInstallerSession 通过内部 StagedSession 适配器向 StagingManager 暴露最小行为契约。StagingManager 不直接依赖 session 的所有字段,只操作状态查询、子 session、verify、install 和 abandon。
3. StagingManager
3.1 注册与提交
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java
@VisibleForTesting
void commitSession(@NonNull StagedSession session) {
createSession(session);
handleCommittedSession(session);
}
@VisibleForTesting
void createSession(@NonNull StagedSession sessionInfo) {
synchronized (mStagedSessions) {
mStagedSessions.append(sessionInfo.sessionId(), sessionInfo);
}
}
private void handleCommittedSession(@NonNull StagedSession session) {
if (session.isSessionReady() && session.containsApexSession()) {
notifyStagedApexObservers();
}
}commitSession() 只是把 staged session 放入 StagingManager 映射并处理已 ready 的 APEX observer;它不等于调用 apexd 激活。预重启验证和 APEX submit 由 session 的后续校验消息链完成。
3.2 session 集合
@GuardedBy("mStagedSessions")
private final SparseArray<StagedSession> mStagedSessions = new SparseArray<>();
void abortSession(@NonNull StagedSession session) {
synchronized (mStagedSessions) {
mStagedSessions.remove(session.sessionId());
}
}StagingManager 只保存 staged session 的内存索引;session 的持久化元数据仍由 PackageInstallerService 写入 install_sessions.xml。两者在重启时通过 sessionId 重新关联。
4. APEX 提交
4.1 apexd 边界
APEX 不能像普通 APK 一样在当前运行系统中直接替换。staged session 将 APEX 文件交给 apexd 建立预重启提交记录,重启后由 apexd 激活,PMS 再根据激活结果继续处理其中的 APK 或普通 APK child。
4.2 APEX 状态
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java
final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
for (int i = 0; i < sessions.size(); i++) {
StagedSession session = sessions.get(i);
if (!session.containsApexSession()) {
continue;
}
final ApexSessionInfo apexSession = apexSessions.get(session.sessionId());
if (apexSession == null || apexSession.isUnknown) {
session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
"apexd did not know anything about a 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,
"Staged session did not activate nor fail.");
}
}恢复时 PMS 会把 apexd 状态分成 unknown、failed、activated/success、仍 staged 和 impossible state。任何非 applied 的异常状态都会显式标记为 activation failed,而不是继续尝试下一次启动。
4.3 激活与 APK
if (hasApex) {
checkInstallationOfApkInApexSuccessful(session);
checkDuplicateApkInApex(session);
snapshotAndRestoreForApexSession(session);
Slog.i(TAG, "APEX packages in session " + session.sessionId()
+ " were successfully activated. Proceeding with APK packages, if any");
}
installApksInSession(session);APEX 激活成功后,StagingManager 才调用 installApksInSession()。一个 staged multi-package session 可以同时包含 APEX 和 APK,但状态检查必须先确认 APEX 部分成功。
5. 重启恢复
5.1 筛选 root
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java
void restoreAndApplyStagedSessionIfNeeded() {
List<StagingManager.StagedSession> stagedSessionsToRestore = new ArrayList<>();
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
if (!session.isStaged()) {
continue;
}
StagingManager.StagedSession stagedSession = session.mStagedSession;
if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId()
&& getSession(stagedSession.getParentSessionId()) == null) {
stagedSession.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
"An orphan staged session is found");
continue;
}
if (!stagedSession.hasParentSessionId() && stagedSession.isCommitted()
&& !stagedSession.isInTerminalState()) {
stagedSessionsToRestore.add(stagedSession);
}
}
}
// Do not hold mSessions while restoreSessions may query sessions for APK install.
mStagingManager.restoreSessions(stagedSessionsToRestore, mPm.isDeviceUpgrading());
}只把 committed、非终态的 root staged session 交给 StagingManager;孤儿 child 直接标 activation failed。调用 restoreSessions 前释放 mSessions 锁,避免恢复过程中的 APK atomic install 反向查询 session 造成锁嵌套。
5.2 启动恢复
public void systemReady() {
mStagingManager.systemReady();
synchronized (mSessions) {
readSessionsLocked();
expireSessionsLocked();
reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL);
}
mSettingsWriteRequest.runNow();
}PMS systemReady 先让 StagingManager 注册 boot receiver,再读取并恢复 session XML,清理过期/孤儿 stage。这里的恢复是 session 元数据恢复,不是立即激活所有 staged session;真正恢复由 restoreAndApplyStagedSessionIfNeeded() 调用。
5.3 恢复前检查
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java
void restoreSessions(@NonNull List<StagedSession> sessions,
boolean isDeviceUpgrading) {
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
return;
}
for (StagedSession session : sessions) {
Preconditions.checkArgument(!session.hasParentSessionId(),
"%d is a child session", session.sessionId());
Preconditions.checkArgument(session.isCommitted(),
"%d is not committed", session.sessionId());
Preconditions.checkArgument(!session.isInTerminalState(),
"%d is in terminal state", session.sessionId());
createSession(session);
}
if (isDeviceUpgrading) {
for (StagedSession session : sessions) {
session.setSessionFailed(PackageManager.INSTALL_ACTIVATION_FAILED,
"Build fingerprint has changed");
}
return;
}boot completed 后不再恢复 staged session;设备正在 OTA 升级时,预重启验证条件可能已经变化,源码选择将 session 全部标记为 activation failed 并提前返回。
6. Checkpoint 恢复
6.1 checkpoint 状态
boolean needsCheckpoint = false;
boolean supportsCheckpoint = false;
try {
supportsCheckpoint = InstallLocationUtils.getStorageManager().supportsCheckpoint();
needsCheckpoint = InstallLocationUtils.getStorageManager().needsCheckpoint();
} catch (RemoteException e) {
throw new IllegalStateException("Failed to get checkpoint status", e);
}
if (sessions.size() > 1 && !supportsCheckpoint) {
throw new IllegalStateException(
"Detected multiple staged sessions on a device without fs-checkpoint support");
}多个 staged session 需要文件系统 checkpoint 支持以保证恢复一致性;vold 查询失败被视为设备处于坏状态,直接抛内部异常。
6.2 resumeSession
private void resumeSession(@NonNull StagedSession session,
boolean supportsCheckpoint, boolean needsCheckpoint) throws PackageManagerException {
final boolean hasApex = session.containsApexSession();
if (supportsCheckpoint && !needsCheckpoint) {
String revertMsg = "Reverting back to safe state. Marking "
+ session.sessionId() + " as failed.";
session.setSessionFailed(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, revertMsg);
return;
}
if (hasApex) {
checkInstallationOfApkInApexSuccessful(session);
checkDuplicateApkInApex(session);
snapshotAndRestoreForApexSession(session);
}
installApksInSession(session);
}设备支持 checkpoint 但当前不在 checkpointing mode 时,StagingManager 不冒险继续安装,而是把 session 标记失败并回到安全状态。checkpoint 条件通过后才恢复 APK 安装。
6.3 APK 安装
private void installApksInSession(@NonNull StagedSession session)
throws PackageManagerException {
try {
// Blocking wait for installation to complete.
session.installSession().get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw (PackageManagerException) e.getCause();
}
}重启恢复阶段会同步等待 PackageInstallerSession.install() 完成,因为 StagingManager 需要据此决定 staged session 是否 applied;内部安装仍由 InstallingSession/InstallPackageHelper 执行。
7. 成功与失败
7.1 APEX 成功标记
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java
if (hasApex) {
if (supportsCheckpoint) {
synchronized (mSuccessfulStagedSessionIds) {
mSuccessfulStagedSessionIds.add(session.sessionId());
}
} else {
mApexManager.markStagedSessionSuccessful(session.sessionId());
}
}有 checkpoint 时先保存 sessionId,等 boot completed 再由 markStagedSessionsAsSuccessful() 通知 apexd;无 checkpoint 时可以立即标记成功。这是 APEX successful 与 PackageInstaller session applied 之间的时序差异。
7.2 安装失败
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java
void onInstallationFailure(StagedSession session, PackageManagerException e,
boolean supportsCheckpoint, boolean needsCheckpoint) {
session.setSessionFailed(e.error, e.getMessage());
abortCheckpoint("Failed to install sessionId: " + session.sessionId()
+ " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint);
}resumeSession 抛出 PackageManagerException 时,session 先记录错误,再尝试 abort checkpoint。APEX 失败还要通过 abortStagedSession() 与 apexd 状态保持同步。
7.3 boot completed
private void markStagedSessionsAsSuccessful() {
synchronized (mSuccessfulStagedSessionIds) {
for (int i = 0; i < mSuccessfulStagedSessionIds.size(); i++) {
mApexManager.markStagedSessionSuccessful(
mSuccessfulStagedSessionIds.get(i));
}
}
}
@VisibleForTesting
void onBootCompletedBroadcastReceived() {
mBootCompleted.complete(null);
BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
}boot completed 触发两个动作:完成待标记成功的 APEX session,并允许依赖 boot completed 的日志/后续处理继续。它不是 session applied 的唯一触发点,APK 安装 future 仍需先完成。
8. Abandon 清理
8.1 暂存 abandon
源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java
void abortCommittedSession(@NonNull StagedSession session) {
final int sessionId = session.sessionId();
if (session.isInTerminalState()) {
return;
}
if (!session.isDestroyed()) {
throw new IllegalStateException(
"Committed session must be destroyed before aborting it");
}
ensureActiveApexSessionIsAborted(session);
if (getStagedSession(sessionId) == null) {
return;
}
if (session.isSessionReady() && session.containsApexSession()) {
notifyStagedApexObservers();
}
abortSession(session);
}已 commit 的 staged session 必须先被 session 标记 destroyed,StagingManager 才允许 abort;APEX active session 也要同步 abort。确认 apexd 和 stage 都不再使用后,才从 StagingManager 映射移除。
8.2 孤儿与悬挂 APEX
恢复时若发现 apexd 有 PMS 不认识的 session,先收集其 ID 并调用 abortStagedSession();若 PMS 有 child 但 parent 缺失,则直接把 child 标记 activation failed。两条清理路径分别修复 apexd 外部状态和 PackageInstaller session 树。
9. 时序与诊断
9.1 完整时序
9.2 诊断顺序
- 确认 session 是否
isStaged()、isCommitted(),以及是 root 还是 child。 - 预重启阶段检查
onVerificationComplete()是否进入mStagingManager.commitSession()。 - 重启后检查 PMS 是否筛选出 committed、非终态 root session。
- APEX 包检查
ApexSessionInfo是 unknown、failed、activated 还是仍 staged。 - 检查 storage checkpoint 的 supports/needs 状态。
- 若 APK 安装失败,转查
installApksInSession()返回的 PackageManagerException。 - 最后检查 session failed/applied、APEX successful 标记和 stage 清理是否一致。
10. 测试与断言
10.1 输入矩阵
| 输入 | 关键断言 | 覆盖范围 |
|---|---|---|
| staged APK session | pre-reboot 只报告 staged,不直接 applied | 两阶段边界 |
| staged APEX session | APEX 状态提交给 apexd | 外部协调 |
| reboot restore root | 只恢复 committed 非终态 root | session 筛选 |
| orphan child | activation failed | session 树清理 |
| build fingerprint changed | 全部 staged session failed | OTA 安全 |
| checkpoint unsupported + multi sessions | 恢复拒绝 | 一致性约束 |
| APEX unknown/failed | activation failed | apexd 状态 |
| APEX activated | 继续 APK child 安装 | APEX/APK 顺序 |
| checkpointing mode 不匹配 | revert 并 failed | 安全恢复 |
| APK install future failed | session failed + abort checkpoint | 安装失败 |
| checkpoint success | boot completed 后 mark successful | 成功时机 |
| committed abandon | abort APEX/stage 后移除 | 清理路径 |
测试必须分别断言 Session staged、SESSION_READY、install future、SESSION_APPLIED 和 ApexManager.markStagedSessionSuccessful(),不能用一个最终 status 覆盖整个跨重启时序。
10.2 现场排查命令
dumpsys package sessions:查看 sessionId、parent、staged、committed、ready/applied/failed。adb shell pm install --staged ...:构造 staged APK/APEX 输入。adb reboot后再次查看 session 状态:区分恢复前和恢复后状态。dumpsys apexservice:对照 apexd 的 staged/activated/failed session。logcat -b system | rg 'StagingManager|PackageInstallerSession|APEX':定位 resume、checkpoint 和 abort。
11. 源码路线
建议按以下顺序阅读:
PackageInstallerSession.onVerificationComplete():普通与 staged 分叉。StagingManager.commitSession():staged session 注册边界。PackageInstallerService.restoreAndApplyStagedSessionIfNeeded():重启筛选 root session。StagingManager.restoreSessions():checkpoint、APEX 状态和恢复循环。resumeSession()/installApksInSession():重启后的 APK 安装交接。onInstallationFailure()/abortCommittedSession():失败与 abandon 清理。markStagedSessionsAsSuccessful()/boot completed:APEX 成功标记时机。
12. 设计收束
staged install 的真实状态链是:
sealed session
-> pre-reboot verification
-> StagingManager tracking / APEX submit
-> SESSION_READY
-> reboot
-> PMS restore root sessions
-> checkpoint + apexd consistency
-> APK install future
-> SESSION_APPLIED / SESSION_FAILED
-> APEX successful mark and cleanup它不是普通 session 的“延迟 commit”:普通 session 的安装 future 在当前启动周期完成,staged session 的可用性要跨越 system_server、文件系统 checkpoint、apeXd 和 PackageInstaller 状态。理解 ready、activated、applied、successful 各自的 owner,才能解释为什么客户端已经收到 staged 成功但应用尚未可用、为什么 APEX 激活失败会连带 APK child 失败,以及为什么 abandon 需要等 stage 不再被使用后才能真正清理。
