Skip to content

APEX 回滚

追踪 Android 17 APEX 激活失败后的 checkpoint、active session revert、CE/DE 数据快照和重启恢复。

基于android-17.0.0_r1
AndroidPMSAPEXRollbackCheckpoint

APEX 回滚 ​

本文承接 Staged APEX,专门讲失败后的恢复路径。重点不是 RollbackManager 的全部公开 API,而是 StagingManager、ApexManager、RollbackManagerInternal、StorageManager checkpoint 和 apexd 如何共同恢复“旧 APEX + 与之兼容的用户数据”。

APEX 回滚至少有两层:代码/挂载回滚由 apexd 的 active session revert 完成;文件系统和用户数据恢复由 checkpoint 与 CE/DE snapshot 完成。只恢复旧 .apex 文件而不恢复数据,可能仍然无法启动旧模块。

1. 回滚触发点 ​

源码文件: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()
                    + " Error: " + e.getMessage(),
            supportsCheckpoint, needsCheckpoint);

    if (!session.containsApexSession()) {
        return;
    }
    if (mApexManager.revertActiveSessions()) {
        mPowerManager.reboot(null);
    }
}

重启后继续安装 APK 子会话、检查 APK-in-APEX 或数据恢复失败时,framework 先把 session 标失败,再尝试 checkpoint abort;如果会话含 APEX,还要求 apexd revert active sessions 并重启。

2. Checkpoint 回滚 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:abortCheckpoint

java
private void abortCheckpoint(
        String failureReason,
        boolean supportsCheckpoint,
        boolean needsCheckpoint) {
    Slog.e(TAG, failureReason);
    try {
        if (supportsCheckpoint && needsCheckpoint) {
            try (BufferedWriter writer =
                    new BufferedWriter(
                            new FileWriter(
                                    mFailureReasonFile))) {
                writer.write(failureReason);
            } catch (Exception e) {
                Slog.w(TAG,
                        "Failed to save failure reason", e);
            }

            if (mApexManager.isApexSupported()) {
                mApexManager.revertActiveSessions();
            }
            InstallLocationUtils.getStorageManager()
                    .abortChanges(
                            "abort-staged-install",
                            false /* retry */);
        }
    } catch (Exception e) {
        if (mApexManager.isApexSupported()) {
            mApexManager.revertActiveSessions();
        }
        mPowerManager.reboot(null);
    }
}

只有设备支持 checkpoint 且当前 needsCheckpoint 时才调用 abortChanges。失败原因先写到 /metadata/staged-install,因为 /data 将被回滚;checkpoint abort 本身异常时,系统仍请求 apexd revert 并重启。

3. Active revert ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:revertActiveSessions

java
boolean revertActiveSessions() {
    try {
        waitForApexService().revertActiveSessions();
        return true;
    } catch (RemoteException re) {
        Slog.e(TAG,
                "Unable to contact apexservice", re);
        return false;
    } catch (Exception e) {
        Slog.e(TAG, e.getMessage(), e);
        return false;
    }
}

Framework 把 apexd revert 结果压缩为 boolean。false 不能理解为“无需回滚”,而是无法确认 active session 已被回退;调用方通常记录错误或进一步重启恢复。

4. APEX 数据快照 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:snapshotAndRestoreForApexSession

java
boolean doSnapshotOrRestore =
        (session.sessionParams().installFlags
                & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
        || session.sessionParams().installReason
                == PackageManager.INSTALL_REASON_ROLLBACK;
if (!doSnapshotOrRestore) {
    return;
}

for (StagedSession apexSession
        : extractApexSessions(session)) {
    String packageName = apexSession.getPackageName();
    snapshotAndRestoreApexUserData(
            packageName, allUsers, rollbackManager);
    for (String apk : mApexManager
            .getApksInApex(packageName)) {
        snapshotAndRestoreApkInApexUserData(
                apk, allUsers, rollbackManager);
    }
}

只有启用 rollback 或正在执行 rollback 的 session 才处理数据。外层 APEX 和 APK-in-APEX 都必须纳入,因为内嵌 APK 不走普通 APK 安装数据快照路径。

5. APK-in-APEX 数据 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:snapshotAndRestoreApkInApexUserData

java
PackageStateInternal ps =
        packageManagerInternal
                .getPackageStateInternal(packageName);
if (ps != null) {
    int appId = ps.getAppId();
    int pccId = ps.getPccId();
    long ceDataInode = ps
            .getUserStateOrDefault(
                    UserHandle.USER_SYSTEM)
            .getCeDataInode();
    int[] installedUsers =
            PackageStateUtils.queryInstalledUsers(
                    ps, allUsers, true);
    rollbackManager.snapshotAndRestoreUserData(
            packageName,
            UserHandle.toUserHandles(installedUsers),
            appId, pccId, ceDataInode,
            ps.getSeInfo(), 0 /* token */);
}

内嵌 APK 只为真实 installed users 处理数据,并传入 appId、PCC、CE inode 和 seInfo。外层 APEX 不需要这些应用沙箱字段,因此两条 snapshot 调用不能合并。

6. CE 数据代理 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:snapshotCeData、restoreCeData

java
public boolean snapshotCeData(
        int userId, int rollbackId,
        String apexPackageName) {
    String moduleName;
    synchronized (mLock) {
        moduleName = mPackageNameToApexModuleName
                .get(apexPackageName);
    }
    if (moduleName == null) {
        return false;
    }
    try {
        waitForApexService().snapshotCeData(
                userId, rollbackId, moduleName);
        return true;
    } catch (Exception e) {
        return false;
    }
}

ApexManager 把 PMS package name 转成 apexd module name,再调用 CE snapshot/restore。扫描映射尚未建立或包名错误时返回 false;CE 用户未解锁时,调用方还必须延迟相应操作。

7. apexd 快照布局 ​

源码文件:system/apex/apexd/apexd.cpp,符号:snapshot/restore helpers

cpp
Result<void> SnapshotCeData(
    const int user_id,
    const int rollback_id,
    const std::string& apex_name) {
  return SnapshotDataDirectory(
      GetCeDataDir(user_id), rollback_id,
      apex_name, false /* pre_restore */);
}

Result<void> RestoreCeData(
    const int user_id,
    const int rollback_id,
    const std::string& apex_name) {
  return RestoreDataDirectory(
      GetCeDataDir(user_id), rollback_id,
      apex_name);
}

apexd 按 rollbackId 和 APEX module name 保存快照;restore 成功后会清理对应 snapshot。pre-restore snapshot 用来保护“恢复动作本身失败”时的原数据,避免回滚过程中再次破坏数据。

8. 快照清理 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:destroy snapshot APIs

java
public boolean destroyDeSnapshots(int rollbackId) {
    try {
        waitForApexService()
                .destroyDeSnapshots(rollbackId);
        return true;
    } catch (Exception e) {
        return false;
    }
}

public boolean destroyCeSnapshotsNotSpecified(
        int userId, int[] retainRollbackIds) {
    try {
        waitForApexService()
                .destroyCeSnapshotsNotSpecified(
                        userId, retainRollbackIds);
        return true;
    } catch (Exception e) {
        return false;
    }
}

DE snapshot 按 rollbackId 清理;CE snapshot 还需要 userId,并能保留指定 rollback IDs。CE 清理受用户解锁状态约束,不能在所有用户上一次完成。

9. 激活失败检测 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:restoreSessions

java
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.isStaged) {
    session.setSessionFailed(
            PackageManager.INSTALL_ACTIVATION_FAILED,
            "Session did not activate at boot");
}

Unknown、activation failed、reverted、revert-in-progress、revert-failed 都属于失败集合。重启后仍是 STAGED 也主动失败,防止无限等待一个无法保证会激活的 session。

10. 混合状态保护 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/StagingManager.java,符号:applied/failed 一致性

java
if (hasAppliedApexSession
        && hasFailedApexSession) {
    abortCheckpoint(
            "Found both applied and failed apex sessions",
            supportsCheckpoint, needsCheckpoint);
    return;
}

同一次启动不能接受一部分 APEX 已应用、另一部分失败的组合。StagingManager 触发整体 checkpoint 回滚,恢复到已知一致的模块集合。

11. 失败原因保存 ​

mFailureReasonFile 位于 /metadata/staged-install/failure_reason.txt,checkpoint 回滚 /data 后仍可读取。apexd 还可能提供 crashingNativeProcess 和 errorMessage;StagingManager 在 boot completed 后记录 revert 事件,避免启动关键路径执行统计 I/O。

12. 失败定位 ​

现象检查点
APEX 回到旧版本但数据异常CE/DE snapshot 是否建立和 restore
checkpoint 未触发supportsCheckpoint、needsCheckpoint
回滚后又重启revertActiveSessions/abortChanges 异常 fallback
某个用户 CE 未恢复用户是否解锁、CE snapshot userId/rollbackId
APK-in-APEX 数据未恢复getApksInApex、installed users、appId/inode
无失败原因/metadata 文件、native failure reason、boot-completed logging

13. 阅读检查 ​

复述:activation/混合 APK 失败 → session failed → 保存 failure reason → apexd revert active sessions → filesystem abort checkpoint → 重启 → 恢复旧 APEX 和数据 snapshot。然后回答:只 revert APEX 文件是否足够?CE snapshot 能否忽略 userId?为什么失败原因不写 /data?答案分别是“不足”“不能”“因为 checkpoint 会回滚 /data”。