Skip to content

Rollback 回滚

追踪安装启用回滚、代码与用户数据快照、可用状态和回滚提交的 Android 17 源码路径。

基于android-17.0.0_r1
AndroidPackageManagerServiceRollbackManager安装源码阅读

Rollback 回滚 ​

本文承接 降级安装 和 安装回调。降级安装回答“能否把版本号变低”,Rollback 回滚回答“系统如何保存旧版本并在故障后可靠地发起一次受控降级”。两者可能都使用低版本 APK,但 owner、触发入口和数据处理完全不同。本文沿 Android 17 源码追踪 INSTALL_ENABLE_ROLLBACK 从安装 session 进入 RollbackManagerServiceImpl,再到代码备份、用户数据快照、AVAILABLE 状态和 commit 回滚;不展开 Package Watchdog 的崩溃评分算法,也不把 APEX staged 激活等同于普通 APK 回滚。

1. 回滚对象 ​

1.1 记录与状态 ​

Rollback 服务为一次原始安装 session 创建一个 Rollback 记录。记录包含 rollback id、原始 session、包的旧/新版本、备份目录、数据策略和当前状态。常见状态是 ENABLING、AVAILABLE、COMMITTED、DELETED;状态由 Rollback 服务线程拥有,持久化由 RollbackStore 负责。

ENABLING 不是“已经可以回滚”:旧代码或用户数据仍可能正在备份。只有整组 package session 都启用成功,服务才调用 makeRollbackAvailable。多包 session 因而共享一个 rollback id,不能把子包单独视为可用回滚。

1.2 数据策略 ​

源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java

java
public static final int INSTALL_ENABLE_ROLLBACK = 0x00040000;
public static final int ROLLBACK_DATA_POLICY_RESTORE = 0;
public static final int ROLLBACK_DATA_POLICY_WIPE = 1;
public static final int ROLLBACK_DATA_POLICY_RETAIN = 2;

策略由 session 参数和 Manifest 策略共同决定。Android 17 的 computeRollbackDataPolicy 保留兼容规则:Manifest 只要不是默认 RESTORE,就优先使用 Manifest 值,否则使用 session 值。RESTORE 需要快照,WIPE 在回滚时清除数据,RETAIN 不恢复旧快照。

2. 启用入口 ​

2.1 安装阶段通知 ​

源码文件:frameworks/base/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java

java
@WorkerThread
private boolean enableRollback(int sessionId) {
    final PackageInstaller installer = mContext.getPackageManager()
            .getPackageInstaller();
    final PackageInstaller.SessionInfo packageSession =
            installer.getSessionInfo(sessionId);
    if (packageSession == null) {
        Slog.e(TAG, "Unable to find session for enabled rollback.");
        return false;
    }

    final PackageInstaller.SessionInfo parentSession =
            packageSession.hasParentSessionId()
                    ? installer.getSessionInfo(packageSession.getParentSessionId())
                    : packageSession;
    Rollback rollback = getRollbackForSession(packageSession.getSessionId());
    if (rollback == null) {
        rollback = createNewRollback(parentSession);
    }
    return enableRollbackForPackageSession(rollback, packageSession)
            ? (rollback.allPackagesEnabled()
                    ? completeEnableRollback(rollback) : true)
            : false;
}

Rollback 服务通过安装 session id 找回 SessionInfo,再以 parent session 作为多包回滚的聚合 owner。allPackagesEnabled() 是 AVAILABLE 的提交屏障;一个子包备份失败,整个 rollback 不可用。这里运行在 rollback worker thread,避免在 PMS 安装锁中执行文件和数据快照。

2.2 权限与白名单 ​

源码文件:frameworks/base/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java

java
private boolean enableRollbackAllowed(String installerPackageName,
        String packageName) {
    if (installerPackageName == null) {
        return false;
    }
    final PackageManager pm = mContext.getPackageManager();
    final boolean manage = pm.checkPermission(Manifest.permission.MANAGE_ROLLBACKS,
            installerPackageName) == PackageManager.PERMISSION_GRANTED;
    final boolean test = pm.checkPermission(Manifest.permission.TEST_MANAGE_ROLLBACKS,
            installerPackageName) == PackageManager.PERMISSION_GRANTED;
    return (isRollbackAllowed(packageName) && manage) || test;
}

private boolean isRollbackAllowed(String packageName) {
    return SystemConfig.getInstance().getRollbackWhitelistedPackages()
            .contains(packageName) || isModule(packageName);
}

回滚不是任意安装器都能开启的功能。生产路径要求包是模块或系统配置白名单包,并且 installer 拥有 MANAGE_ROLLBACKS;测试权限可绕过白名单。权限检查发生在启用阶段,失败不会留下 AVAILABLE 记录。

3. 代码快照 ​

3.1 旧版本路径 ​

源码文件:frameworks/base/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java

java
final PackageInfo pkgInfo = mContext.getPackageManager().getPackageInfo(
        packageName, PackageManager.MATCH_APEX);
final AndroidPackage newPackage = session.getPackageLite();

return rollback.enableForPackage(packageName,
        newPackage.getVersionCode(),
        pkgInfo.getLongVersionCode(),
        isApex,
        pkgInfo.applicationInfo.sourceDir,
        pkgInfo.applicationInfo.splitSourceDirs,
        rollbackDataPolicy,
        session.rollbackImpactLevel);

versionRolledBackFrom 是当前已安装版本,versionRolledBackTo 是本次安装前保存的旧版本。代码路径来自已安装包的 ApplicationInfo.sourceDir 和 split paths,而不是新 session 的 staging 路径。RollbackStore 随后把旧 APK 复制到 rollback backup 目录,确保新 data APK 被删除后仍能构造回滚安装。

3.2 多包顺序 ​

嵌入 APK-in-APEX 时,源码要求先启用 embedded APK,再启用 embedding APEX;否则 APEX 已成功启用而内部 APK 备份失败,会留下不一致的 rollback。Rollback.allPackagesEnabled() 不把 APK-in-APEX 当成独立计数项,这个特殊顺序不能由普通多包循环推断出来。

4. 用户数据 ​

4.1 快照与恢复接口 ​

源码文件:frameworks/base/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java

java
public void snapshotAndRestoreUserData(String packageName, int[] userIds,
        int appId, int pccId, long ceDataInode, String seInfo, int token) {
    if (Binder.getCallingUid() != Process.SYSTEM_UID) {
        throw new SecurityException(
                "snapshotAndRestoreUserData may only be called by the system.");
    }
    getHandler().post(() -> {
        snapshotUserDataInternal(packageName, userIds);
        restoreUserDataInternal(packageName, userIds, appId, pccId, seInfo);
        if (token > 0) {
            final PackageManagerInternal pmi =
                    LocalServices.getService(PackageManagerInternal.class);
            pmi.finishPackageInstall(token, false);
        }
    });
}

该接口只接受 system UID,因为它直接操作 app data 快照。安装流程传入正 token 时,Rollback 服务完成快照/恢复后调用 finishPackageInstall,PMS 才继续 POST_INSTALL;token 为非正值则是独立 API 调用,不通知安装流程。

4.2 与安装恢复的关系 ​

InstallPackageHelper.restoreAndPostInstall 在成功更新后会调用 performRollbackManagerRestore。如果 rollback 服务需要为旧数据创建快照,PMS 会暂缓 POST_INSTALL;完成后再回到 PackageHandler。这使“安装成功”与“用户数据已经按照回滚策略处理完成”保持同一可观察边界。

5. 可用与提交 ​

5.1 make available ​

源码文件:frameworks/base/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java

java
@WorkerThread
private void makeRollbackAvailable(Rollback rollback) {
    rollback.makeAvailable();
    mPackageHealthObserver.notifyRollbackAvailable(rollback.info);

    if (rollback.info.getRollbackImpactLevel()
            == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
        final int rollbackId = rollback.info.getRollbackId();
        final List<String> packages = rollback.getPackageNames();
        mPackageWatchdog.startExplicitHealthCheck(packages,
                mRollbackLifetimeDurationInMillis, mPackageHealthObserver);
    }
    runExpiration();
}

AVAILABLE 之后,Package Health Observer 才能观察包是否频繁崩溃;低用户影响等级会启动显式健康检查。回滚寿命检查也从这里开始。健康观察不是回滚本身,观察结果只是后续触发 commitRollback 的一个来源。

5.2 commit API ​

源码文件:frameworks/base/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java

java
public void commitRollback(int rollbackId,
        ParceledListSlice causePackages, String callerPackageName,
        IntentSender statusReceiver) {
    enforceManageRollbacks("commitRollback");
    final int callingUid = Binder.getCallingUid();
    mContext.getSystemService(AppOpsManager.class)
            .checkPackage(callingUid, callerPackageName);
    getHandler().post(() -> commitRollbackInternal(rollbackId,
            causePackages.getList(), callerPackageName, statusReceiver));
}

private void commitRollbackInternal(int rollbackId,
        List<VersionedPackage> causePackages,
        String callerPackageName, IntentSender statusReceiver) {
    final Rollback rollback = getRollbackForId(rollbackId);
    if (rollback == null) {
        sendFailure(mContext, statusReceiver,
                RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
                "Rollback unavailable");
        return;
    }
    rollback.commit(mContext, causePackages,
            callerPackageName, statusReceiver);
}

公开 API 只负责权限、AppOps 包名校验和切换到 worker thread;真正的回滚安装由 Rollback.commit 执行。找不到 rollback 时通过 IntentSender 返回 unavailable,不会创建一个新的降级 session。causePackages 记录触发故障的包版本,供诊断和健康观察使用。

6. 失败与清理 ​

6.1 启用失败 ​

源码文件:frameworks/base/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java

java
if (Intent.ACTION_CANCEL_ENABLE_ROLLBACK.equals(intent.getAction())) {
    final int sessionId = intent.getIntExtra(
            PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_SESSION_ID, -1);
    final Rollback rollback = getRollbackForSession(sessionId);
    if (rollback != null && rollback.isEnabling()) {
        mRollbacks.remove(rollback);
        deleteRollback(rollback, "Rollback canceled");
    }
}

启用超时或取消只删除 ENABLING 记录及其备份目录;已经 AVAILABLE 的 rollback 不走这条取消分支。启用失败返回给 PMS 的 code 是 ENABLE_ROLLBACK_FAILED,但安装是否继续由安装流程自己的策略决定,不能把回滚准备失败自动等同于 APK 安装失败。

6.2 过期清理 ​

Rollback 服务通过 runExpiration() 遍历时间戳和 lifetime,删除过期记录和代码/数据备份。时间变化广播会修正相对 boot time,避免用户修改系统时钟直接延长 rollback 生命周期。删除完成后状态进入 DELETED,后续 commit 返回 unavailable。

7. 验证方法 ​

7.1 启用结果 ​

使用具备 MANAGE_ROLLBACKS 的模块安装器创建带 INSTALL_ENABLE_ROLLBACK 的 session。输入为一组合法 APK、旧版本已安装状态和 RESTORE/WIPE/RETAIN 策略。关键断言是安装成功后能查询到 AVAILABLE rollback,记录包含 old/new version 和对应 package;权限不足或非白名单包应在启用阶段失败,而不是产生半成品 AVAILABLE 记录。

7.2 提交回滚 ​

调用 commitRollback(rollbackId, causePackages, callerPackageName, statusReceiver),接收 IntentSender 结果并再次查询包版本。RESTORE 策略应恢复快照前的数据,WIPE 应清除用户数据,RETAIN 应保留当前数据。版本切换成功不证明应用自身数据库兼容性,数据断言必须独立检查。

7.3 中断与重启 ​

在代码备份或用户数据快照阶段停止 DataLoader/安装器,观察 rollback 是否停留在 ENABLING 并被清理;在 staged/Apex 场景重启设备后,检查 rollback store 是否重新加载。重点检查 RollbackStore.loadRollbacks()、状态字段和 backup 目录,不能只依据安装器 UI 的失败文本。

8. 源码路线 ​

  1. PackageManager.INSTALL_ENABLE_ROLLBACK 与三个 data policy 常量。
  2. RollbackManagerServiceImpl.enableRollback:session 聚合与 AVAILABLE 屏障。
  3. enableRollbackAllowed / isRollbackAllowed:权限和白名单边界。
  4. Rollback.enableForPackage:旧/新版本、代码路径和策略记录。
  5. snapshotAndRestoreUserData:system UID、worker thread 和 PMS token。
  6. makeRollbackAvailable:健康观察与寿命管理。
  7. commitRollback / commitRollbackInternal:权限、状态查找和异步提交。
  8. RollbackStore 与删除路径:代码、数据备份及过期清理。

Rollback 的主线可以概括为:安装时先保存“能够回去”的代码和数据,整组安装成功后才公布 AVAILABLE;故障触发时由受权限保护的 commit API 发起回滚,再把旧包重新安装并按策略处理用户数据。它使用降级安装能力,但通过独立记录、快照和健康观察把一次危险的版本倒退变成可审计、可清理的生命周期。