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
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
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
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
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
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
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
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
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
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 一致性
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”。
