Skip to content

StagedSession 暂存安装

追踪 staged session 的预重启封存、APEX 提交、重启恢复、checkpoint 和失败回滚。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageInstallerStagingManagerAPEX源码阅读

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负责状态
PackageInstallerSessionsealed、committed、ready/applied/failed、status receiver
StagingManagerstaged session 集合、重启恢复、APEX/checkpoint 协调
ApexManager/apeXdAPEX staged、activated、successful、reverted

2. Session 进入暂存 ​

2.1 验证完成分叉 ​

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

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 暂存适配器 ​

java
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

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 集合 ​

java
@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

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 ​

java
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

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 启动恢复 ​

java
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

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 状态 ​

java
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 ​

java
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 安装 ​

java
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

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

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 ​

java
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

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 诊断顺序 ​

  1. 确认 session 是否 isStaged()、isCommitted(),以及是 root 还是 child。
  2. 预重启阶段检查 onVerificationComplete() 是否进入 mStagingManager.commitSession()。
  3. 重启后检查 PMS 是否筛选出 committed、非终态 root session。
  4. APEX 包检查 ApexSessionInfo 是 unknown、failed、activated 还是仍 staged。
  5. 检查 storage checkpoint 的 supports/needs 状态。
  6. 若 APK 安装失败,转查 installApksInSession() 返回的 PackageManagerException。
  7. 最后检查 session failed/applied、APEX successful 标记和 stage 清理是否一致。

10. 测试与断言 ​

10.1 输入矩阵 ​

输入关键断言覆盖范围
staged APK sessionpre-reboot 只报告 staged,不直接 applied两阶段边界
staged APEX sessionAPEX 状态提交给 apexd外部协调
reboot restore root只恢复 committed 非终态 rootsession 筛选
orphan childactivation failedsession 树清理
build fingerprint changed全部 staged session failedOTA 安全
checkpoint unsupported + multi sessions恢复拒绝一致性约束
APEX unknown/failedactivation failedapexd 状态
APEX activated继续 APK child 安装APEX/APK 顺序
checkpointing mode 不匹配revert 并 failed安全恢复
APK install future failedsession failed + abort checkpoint安装失败
checkpoint successboot completed 后 mark successful成功时机
committed abandonabort 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. 源码路线 ​

建议按以下顺序阅读:

  1. PackageInstallerSession.onVerificationComplete():普通与 staged 分叉。
  2. StagingManager.commitSession():staged session 注册边界。
  3. PackageInstallerService.restoreAndApplyStagedSessionIfNeeded():重启筛选 root session。
  4. StagingManager.restoreSessions():checkpoint、APEX 状态和恢复循环。
  5. resumeSession()/installApksInSession():重启后的 APK 安装交接。
  6. onInstallationFailure()/abortCommittedSession():失败与 abandon 清理。
  7. markStagedSessionsAsSuccessful()/boot completed:APEX 成功标记时机。

12. 设计收束 ​

staged install 的真实状态链是:

text
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 不再被使用后才能真正清理。