Skip to content

降级安装

解释 Android 17 如何判定 APK 降级、过滤授权标志并分别处理普通包、系统包、保留数据包和 APEX。

基于android-17.0.0_r1
AndroidPackageManagerService安装版本校验源码阅读

降级安装 ​

本文承接 安装前检查 和 系统应用更新,只讨论“新 APK 的版本低于已有包”时 PMS 如何作出决定。重点是 Android 17 的真实代码:谁设置降级意图,谁授予降级权限,PackageManagerServiceUtils 如何比较版本,以及 data 被保留但当前包对象不存在时为什么仍要检查。本文不展开 RollbackManager 的快照创建,也不把 APEX 的激活策略混同为 APK 降级。

降级保护的直接理由可以从源码注释读出:新包会继续使用旧包留下的应用数据,因此系统需要防止未经授权的旧代码读取新版本产生的数据。PMS 把“请求降级”和“允许降级”拆成两个标志,并且把最终版本比较放在安装前的替换校验中;即使调用者成功写入 session,降级也可能在 commit 前被拒绝。

1. 决策入口 ​

1.1 两个标志 ​

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

java
// 仅表示调用者明确请求执行降级检查路径
public static final int INSTALL_REQUEST_DOWNGRADE = 0x00000080;

// 在 user build 上授予降级权限;隐藏 API
public static final int INSTALL_ALLOW_DOWNGRADE = 0x00100000;

// 新版本号低于当前版本且未获允许时的返回码
public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25;

INSTALL_REQUEST_DOWNGRADE 是意图,INSTALL_ALLOW_DOWNGRADE 是授权。只设置后者并不会让一个未请求降级的安装自动走降级路径;反之,只请求降级也不足以在 user build 上绕过保护。错误码由 checkDowngrade 抛出,再由安装会话转换成对调用方可见的失败结果。

1.2 调用方归一化 ​

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

java
if (Build.IS_DEBUGGABLE || PackageManagerServiceUtils.isSystemOrRoot(callingUid)) {
    // 可调试构建或 system/root 调用方保留授权标志
    params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
    // 普通调用方不能自行携带该隐藏授权
    params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
}

这段代码发生在 session 创建参数归一化期间,owner 是 PackageInstallerService 的调用者身份检查,而不是 APK 自己声明的属性。callingUid 为 system/root 时可保留授权;普通应用即使构造了相同整数,也会在进入 InstallPackageHelper 前被清除。可调试构建会保留该能力用于测试,但仍要求 INSTALL_REQUEST_DOWNGRADE,使 debug 行为尽量接近正式构建的调用形状。

图中的“允许”只表示不因版本变低而立即拒绝;签名、权限模型、系统包预装版本和 APEX 激活仍有自己的检查。

2. 授权函数 ​

2.1 授权函数 ​

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

java
public static boolean isDowngradePermitted(int installFlags,
        boolean isAppDebuggable) {
    // 即使 debug,也必须由调用者明确请求降级
    final boolean downgradeRequested =
            (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0;
    if (!downgradeRequested) {
        return false;
    }

    // debug 平台或当前已安装版本可调试时允许降级
    final boolean isDebuggable = Build.IS_DEBUGGABLE || isAppDebuggable;
    if (isDebuggable) {
        return true;
    }

    // user build 的非 debug 包必须拥有 system_server 授权
    return (installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) != 0;
}

函数只读取两个输入:安装标志和“当前已安装版本是否 debuggable”。它不比较版本号,也不读 PackageSetting;因此调用方必须在之后调用 checkDowngrade。一个常见误解是“debuggable app 永远可降级”,源码实际仍要求 INSTALL_REQUEST_DOWNGRADE,只是免除了 INSTALL_ALLOW_DOWNGRADE。

2.2 条件真值 ​

构建/当前包请求标志授权标志函数结果
user,非 debug0任意false
user,非 debug10false
user,非 debug11true
user,当前包 debug10true
debuggable build10true
debuggable build0任意false

这张表只覆盖“是否允许进行降级版本比较”,不代表签名、split revision 或系统镜像基线一定通过。

3. 版本比较 ​

3.1 校验入口 ​

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

java
Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
        long requiredInstalledVersionCode, int installFlags) {
    if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
        return verifyReplacingVersionCodeForApex(
                pkgLite, requiredInstalledVersionCode, installFlags);
    }

    final String packageName = pkgLite.packageName;
    synchronized (mPm.mLock) {
        final PackageSetting dataOwnerPs =
                mPm.mSettings.getPackageLPr(packageName);
        if (dataOwnerPs == null) {
            if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
                return Pair.create(INSTALL_FAILED_WRONG_INSTALLED_VERSION,
                        "Required installed version does not match absent package");
            }
            return Pair.create(INSTALL_SUCCEEDED, null);
        }

requiredInstalledVersionCode 是调用者可选的“我预期替换哪个版本”条件,和“新版本是否低于当前版本”是两件事。前者不匹配返回 INSTALL_FAILED_WRONG_INSTALLED_VERSION;后者由 checkDowngrade 决定。dataOwnerPs 即使没有 AndroidPackage,也可能因为 DELETE_KEEP_DATA 或归档状态而保留数据和版本记录。

3.2 普通 APK ​

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

java
AndroidPackage dataOwnerPkg = dataOwnerPs.getPkg();

if (dataOwnerPkg == null) {
    // 没有当前运行对象,但仍可能有保留数据
    if (!PackageManagerServiceUtils.isDowngradePermitted(
            installFlags, dataOwnerPs.isDebuggable())) {
        try {
            PackageManagerServiceUtils.checkDowngrade(dataOwnerPs, pkgLite);
        } catch (PackageManagerException e) {
            return Pair.create(INSTALL_FAILED_VERSION_DOWNGRADE,
                    "Downgrade detected on app uninstalled with DELETE_KEEP_DATA: "
                            + e.getMessage());
        }
    }
} else if (!dataOwnerPkg.isSdkLibrary()) {
    if (!PackageManagerServiceUtils.isDowngradePermitted(
            installFlags, dataOwnerPkg.isDebuggable())) {
        try {
            PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
        } catch (PackageManagerException e) {
            return Pair.create(INSTALL_FAILED_VERSION_DOWNGRADE,
                    "Downgrade detected: " + e.getMessage());
        }
    }
}

普通 APK 的比较对象是当前 data owner。dataOwnerPkg == null 并不是“没有约束”,恰恰相反,保留数据意味着未来安装的 APK 会接管旧数据,所以仍需使用 PackageSetting 中的版本和 split 信息检查。SDK library 在此分支跳过普通应用比较,后续由库专用规则处理。

3.3 比较字段 ​

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

java
private static void checkDowngrade(long beforeVersionCode,
        int beforeBaseRevisionCode, String[] beforeSplitNames,
        int[] beforeSplitRevisionCodes, PackageInfoLite after)
        throws PackageManagerException {
    if (after.getLongVersionCode() < beforeVersionCode) {
        throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                "Update version code " + after.versionCode
                        + " is older than current " + beforeVersionCode);
    } else if (after.getLongVersionCode() == beforeVersionCode) {
        if (after.baseRevisionCode < beforeBaseRevisionCode) {
            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                    "Update base revision code is older than current");
        }
        for (int i = 0; i < after.splitNames.length; i++) {
            final int j = ArrayUtils.indexOf(beforeSplitNames, after.splitNames[i]);
            if (j != -1
                    && after.splitRevisionCodes[i] < beforeSplitRevisionCodes[j]) {
                throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                        "Update split revision code is older than current");
            }
        }
    }
}

比较顺序是 long version code、base revision code、同名 split revision code。版本号相等但 base revision 变低,同样属于降级;只降低一个同名 split,也会失败。新 APK 增加一个此前不存在的 split 不会触发这段“同名 split 低于旧值”的比较,但仍必须通过 split 结构和签名校验。

4. 系统包边界 ​

4.1 预装版本比较 ​

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

java
} else if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
    if (!PackageManagerServiceUtils.isDowngradePermitted(
            installFlags, dataOwnerPkg.isDebuggable())) {
        PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
    } else if (dataOwnerPs.isSystem()) {
        // 已授权降级仍不能低于系统镜像中的预装版本
        PackageSetting disabledPs =
                mPm.mSettings.getDisabledSystemPkgLPr(dataOwnerPs);
        if (disabledPs != null) {
            dataOwnerPkg = disabledPs.getPkg();
        }
        if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) {
            PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
        }
    }
}

系统包有两个版本参照物:当前 /data 更新包和 mDisabledSysPackages 中的原始系统包。即使 user build 的 system_server 授予了降级权限,新的 APK 仍不能低于预装版本;只有 debuggable 平台或 debuggable 原包才放宽这条系统镜像基线。这里正是系统应用更新文章中“保存原包”的消费者之一。

4.2 与安装位置无关 ​

降级授权不会改变系统包必须安装在内部存储的限制。preparePackage 仍会拒绝 external storage 和 instant app 形式的系统包更新;因此“版本检查通过”不等于“安装一定成功”,位置、签名、权限模型和 native library 仍在后续阶段生效。

5. APEX 分支 ​

5.1 活跃版本 ​

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

java
private Pair<Integer, String> verifyReplacingVersionCodeForApex(
        PackageInfoLite pkgLite, long requiredInstalledVersionCode,
        int installFlags) {
    final PackageInfo activePackage = mPm.snapshotComputer().getPackageInfo(
            pkgLite.packageName, PackageManager.MATCH_APEX, UserHandle.USER_SYSTEM);
    if (activePackage == null) {
        return Pair.create(INSTALL_FAILED_PACKAGE_CHANGED,
                "Attempting to install new APEX package " + pkgLite.packageName);
    }

    final long activeVersion = activePackage.getLongVersionCode();
    final boolean appDebuggable = (activePackage.applicationInfo.flags
            & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
    if (!PackageManagerServiceUtils.isDowngradePermitted(
            installFlags, appDebuggable)
            && pkgLite.getLongVersionCode() < activeVersion) {
        return Pair.create(INSTALL_FAILED_VERSION_DOWNGRADE,
                "Downgrade of APEX package is not allowed");
    }
    return Pair.create(INSTALL_SUCCEEDED, null);
}

APEX 的当前版本来自 PMS snapshot 的 MATCH_APEX 查询,而不是普通 APK 的 PackageSetting.getPkg()。该函数只做 required version 和基本降级检查,真正的激活、回滚和重启生效由 staged/APEX 管道继续处理,不能用 APK split revision 的解释替代。

6. 时序与失败 ​

失败发生在替换版本校验阶段,旧包仍是当前消费者,新的 APK 不会进入 mPackages。若校验通过,才会进入后续 scan/reconcile/commit;因此排查“降级命令返回失败但旧版本仍能运行”时,应先看 INSTALL_FAILED_VERSION_DOWNGRADE 和 verifyReplacingVersionCode,不应先怀疑组件注册。

7. 验证方法 ​

7.1 普通包矩阵 ​

准备同一签名的两个 APK,令 before.longVersionCode = 20、after.longVersionCode = 10,分别在 userdebug 和 user 构建执行带降级请求与不带降级请求的安装。关键断言:未请求降级时始终失败;userdebug 请求降级后进入 commit;user 构建还需要 system_server 授权。该实验验证授权函数和调用方过滤,不证明应用数据 schema 可兼容。

7.2 修订号与数据 ​

构造两个 long version code 相同但 base revision 或同名 split revision 降低的 APK,安装后观察仍返回 INSTALL_FAILED_VERSION_DOWNGRADE。再对应用执行保留数据卸载,然后安装更低版本;源码分支要求 PMS 仍用 PackageSetting 做比较。这个实验验证“无 AndroidPackage 仍检查版本”的边界,不验证最终 app data 是否能被旧代码正确读取。

bash
adb shell pm path <package.name>
adb shell dumpsys package <package.name> | grep -E 'versionCode|flags|User'

dumpsys 的版本和用户状态用于确认失败前后当前包没有被替换;若版本已经变化,说明请求走到了允许降级后的 commit,应继续检查签名和安装结果回调。

8. 源码路线 ​

  1. PackageManager.INSTALL_REQUEST_DOWNGRADE 与 INSTALL_ALLOW_DOWNGRADE:先明确意图和授权的区别。
  2. PackageInstallerService.createSessionInternal:确认调用 UID 如何过滤隐藏授权。
  3. PackageManagerServiceUtils.isDowngradePermitted:建立 debug/user build 的真值表。
  4. InstallPackageHelper.verifyReplacingVersionCode:区分 required version、普通 APK、系统包和 APEX。
  5. PackageManagerServiceUtils.checkDowngrade:逐字段跟踪 version、base revision 和 split revision。
  6. commitPackagesLocked:确认只有版本检查通过后,新包才会替换当前消费者。

降级安装因此是一条“授权先行、比较细分、提交延后”的路径:标志由调用方提出,服务按身份裁剪,工具函数决定是否允许比较,安装助手依据不同包类型选择比较对象,只有全部通过后才改变运行时包表。