Skip to content

Staged APEX

追踪 Android 17 StagingManager 如何协调 staged APEX 会话、重启恢复、checkpoint、APK 子会话和失败回滚。

基于android-17.0.0_r1
AndroidPMSAPEXStagingManagerRollback

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,符号:核心字段

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

java
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

java
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

java
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

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());
        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

java
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 的状态检查

java
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,符号:全局一致性检查

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

java
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

java
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

java
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

java
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 仍是 STAGEDapexd 未在 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?三个答案分别对应激活确定性、原子版本组合和保留回滚窗口。