降级安装
本文承接 安装前检查 和 系统应用更新,只讨论“新 APK 的版本低于已有包”时 PMS 如何作出决定。重点是 Android 17 的真实代码:谁设置降级意图,谁授予降级权限,PackageManagerServiceUtils 如何比较版本,以及 data 被保留但当前包对象不存在时为什么仍要检查。本文不展开 RollbackManager 的快照创建,也不把 APEX 的激活策略混同为 APK 降级。
降级保护的直接理由可以从源码注释读出:新包会继续使用旧包留下的应用数据,因此系统需要防止未经授权的旧代码读取新版本产生的数据。PMS 把“请求降级”和“允许降级”拆成两个标志,并且把最终版本比较放在安装前的替换校验中;即使调用者成功写入 session,降级也可能在 commit 前被拒绝。
1. 决策入口
1.1 两个标志
源码文件:frameworks/base/core/java/android/content/pm/PackageManager.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
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
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,非 debug | 0 | 任意 | false |
| user,非 debug | 1 | 0 | false |
| user,非 debug | 1 | 1 | true |
| user,当前包 debug | 1 | 0 | true |
| debuggable build | 1 | 0 | true |
| debuggable build | 0 | 任意 | false |
这张表只覆盖“是否允许进行降级版本比较”,不代表签名、split revision 或系统镜像基线一定通过。
3. 版本比较
3.1 校验入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.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
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
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
} 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
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 是否能被旧代码正确读取。
adb shell pm path <package.name>
adb shell dumpsys package <package.name> | grep -E 'versionCode|flags|User'dumpsys 的版本和用户状态用于确认失败前后当前包没有被替换;若版本已经变化,说明请求走到了允许降级后的 commit,应继续检查签名和安装结果回调。
8. 源码路线
PackageManager.INSTALL_REQUEST_DOWNGRADE与INSTALL_ALLOW_DOWNGRADE:先明确意图和授权的区别。PackageInstallerService.createSessionInternal:确认调用 UID 如何过滤隐藏授权。PackageManagerServiceUtils.isDowngradePermitted:建立 debug/user build 的真值表。InstallPackageHelper.verifyReplacingVersionCode:区分 required version、普通 APK、系统包和 APEX。PackageManagerServiceUtils.checkDowngrade:逐字段跟踪 version、base revision 和 split revision。commitPackagesLocked:确认只有版本检查通过后,新包才会替换当前消费者。
降级安装因此是一条“授权先行、比较细分、提交延后”的路径:标志由调用方提出,服务按身份裁剪,工具函数决定是否允许比较,安装助手依据不同包类型选择比较对象,只有全部通过后才改变运行时包表。
