OTA 系统应用更新
本文承接 系统应用更新、系统应用卸载 和包扫描模块。在线更新时,PMS 已经知道旧系统包和新 data 包;OTA 重启后的问题不同:新的只读分区可能带来另一个版本、不同路径甚至不同 shared UID 设置,而 /data/app 仍保留升级前的覆盖包。本文沿 Android 17 启动扫描追踪 PMS 如何重新裁决两者,重点说明 isSystemPkgBetter、mExpectingBetter、disabled system package 清理和 data 扫描失败后的恢复。
1. 扫描顺序
1.1 system 先于 data
源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
mInstallPackageHelper.prepareSystemPackageCleanUp(
packageSettings,
mPossiblyDeletedUpdatedSystemApps,
mExpectingBetter,
userIds);
...
scanDirTracedLI(mPm.getAppInstallDir(), 0,
mScanFlags | SCAN_REQUIRE_KNOWN,
packageParser, mExecutorService, null);
...
fixSystemPackages(userIds);
mExpectingBetter.clear();启动先完成 system 分区扫描与初步清理,再扫描 /data/app,最后调用 fixSystemPackages 收尾。这个顺序让 PMS 可以暂时移除刚扫描的 system 包,等待 data 覆盖包出现;若 data 包没有出现,最后再回退 system 路径。
1.2 三份状态
OTA 裁决同时读取:本轮刚解析的 system APK、Settings 中当前/历史 PackageSetting,以及 disabled system record。mPackages 只保存本轮目前可见的活跃对象;mExpectingBetter 是启动期间的临时恢复表,不会持久化到下一次开机。
2. system 裁决
2.1 识别更新系统包
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
final PackageSetting pkgSetting = originalPkgSetting == null
? installedPkgSetting : originalPkgSetting;
final boolean pkgAlreadyExists = pkgSetting != null;
final String disabledPkgName = pkgAlreadyExists
? pkgSetting.getPackageName() : parsedPackage.getPackageName();
synchronized (mPm.mLock) {
if (scanSystemPartition && !pkgAlreadyExists
&& mPm.mSettings.getDisabledSystemPkgLPr(
disabledPkgName) != null) {
// data 包设置意外丢失,只剩 disabled 记录时修复不一致状态
mPm.mSettings.removeDisabledSystemPackageLPw(disabledPkgName);
}
disabledPkgSetting =
mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName);
isSystemPkgUpdated = disabledPkgSetting != null;
}disabled record 是“这个 system 包曾被 data 包覆盖”的事实来源。若当前包设置已经丢失但 disabled 记录仍在,源码先移除孤立 disabled 状态,让 system APK 按普通预装包重新安装;否则后续扫描会一直把它当成被覆盖包。
2.2 better 条件
final boolean newPkgChangedPaths = pkgAlreadyExists
&& !pkgSetting.getPathString().equals(parsedPackage.getPath());
final boolean newPkgVersionGreater = pkgAlreadyExists
&& parsedPackage.getLongVersionCode() > pkgSetting.getVersionCode();
final boolean newSharedUserSetting = pkgAlreadyExists
&& initialScanRequest.mOldSharedUserSetting
!= initialScanRequest.mSharedUserSetting;
final boolean isSystemPkgBetter = scanSystemPartition
&& isSystemPkgUpdated
&& newPkgChangedPaths
&& (newPkgVersionGreater || newSharedUserSetting);system 包胜出不仅依赖版本更高。若新的 system APK 使用不同 shared user 设置,PMS 信任只读系统镜像,即使版本号更小也切回 system。newPkgChangedPaths 避免把同一路径的重新扫描误判成 OTA 替换。
3. system 胜出
3.1 清理 data 覆盖
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (isSystemPkgBetter) {
synchronized (mPm.mLock) {
// 只移除当前运行对象,随后由 system 扫描结果重新注册
mPm.mPackages.remove(pkgSetting.getPackageName());
}
mRemovePackageHelper.cleanUpResources(
pkgSetting.getPackageName(),
new File(pkgSetting.getPathString()));
synchronized (mPm.mLock) {
mPm.mSettings.enableSystemPackageLPw(
pkgSetting.getPackageName());
}
}这里先从 mPackages 移除旧 data 对象,再清理 data code path,最后把 disabled system setting 重新启用。当前正在解析的 system APK 随后继续证书收集、扫描和注册,因此消费者最终只看到 system 对象。
3.2 用户数据边界
此处调用 cleanUpResources 清理 APK/code 资源,不等于无条件销毁用户数据。用户 installed 状态和 app data 仍从已有设置传播;shared UID 改变、appId 不一致等情况另有兼容检查。OTA 将 system APK 设为 active 不能证明应用自己的数据库一定向后兼容。
4. data 胜出
4.1 system 跳过
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
if (scanSystemPartition && isSystemPkgUpdated
&& !isSystemPkgBetter) {
if (needSignatureMatchToSystem(parsedPackage.getPackageName())) {
final ParseResult<SigningDetails> result =
ParsingPackageUtils.getSigningDetails(
ParseTypeImpl.forDefaultParsing(), parsedPackage,
Flags.trustSystemApkSignatures());
if (result.isError()) {
throw new PrepareFailure(
"Failed collect during scanPackageForInitLI",
result.getException());
}
disabledPkgSetting.setSigningDetails(result.getResult());
}
parsedPackage.hideAsFinal();
throw PackageManagerException.ofInternalError(
"updated version better than this system package",
INTERNAL_ERROR_UPDATED_VERSION_BETTER_THAN_SYSTEM);
}这个异常是有意的控制流:system APK 不进入 live 数据结构,稍后的 /data/app 扫描会重新注册更新包。部分包还会把 immutable system APK 的签名写入 disabled setting,供 data 包 reconcile 时校验,避免 OTA 后 data 覆盖包绕过新的系统签名基线。
4.2 APK-in-APEX 条件
APK-in-APEX 有 feature flag 分支:为了不因 data 中较新 APK 而让整个 APEX 安装失败,源码可能不抛上述异常,但仍完整扫描 APEX 内 APK,以保证将来 data 包被删除时 system 副本可用。不能把普通 /system/app 的跳过规则直接套给所有 APK-in-APEX。
5. expectingBetter
5.1 扫描后分类
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
final AndroidPackage scannedPkg = mPm.mPackages.get(packageName);
final PackageSetting disabledPs =
mPm.mSettings.getDisabledSystemPkgLPr(packageName);
if (scannedPkg != null && disabledPs != null) {
// OTA 扫描出了 system 包,但还期待 data 覆盖版本
mRemovePackageHelper.removePackage(scannedPkg, true);
expectingBetter.put(packageName, ps.getPath());
} else if (scannedPkg == null && disabledPs == null) {
// system APK 已消失且没有可恢复记录
mRemovePackageHelper.removePackageData(ps, userIds);
} else if (disabledPs != null) {
if (disabledPs.getPath() == null
|| !disabledPs.getPath().exists()
|| disabledPs.getPkg() == null) {
possiblyDeletedUpdatedSystemApps.add(packageName);
} else {
expectingBetter.put(packageName, disabledPs.getPath());
}
}expectingBetter 保存 fallback system path,等待 data 扫描;possiblyDeletedUpdatedSystemApps 表示 OTA 已从 system 镜像删除原包。两者回答不同问题:前者处理 data 副本失踪,后者处理 system 身份已经消失。
5.2 data 未出现
for (int i = 0; i < expectingBetterPackages.size(); i++) {
final String packageName = expectingBetterPackages.keyAt(i);
if (mPm.mPackages.containsKey(packageName)) {
continue;
}
final File scanFile = expectingBetterPackages.valueAt(i);
final Pair<Integer, Integer> flags =
mPm.getSystemPackageRescanFlagsAndReparseFlags(
scanFile, systemScanFlags, systemParseFlags);
if (flags.first == 0) {
continue;
}
mPm.mSettings.enableSystemPackageLPw(packageName);
try (PackageManagerTracedLock installLock =
mPm.mInstallLock.acquireLock()) {
final AndroidPackage newPkg = initPackageTracedLI(
scanFile, flags.second, flags.first);
if (newPkg.isStub()) {
stubSystemApps.add(packageName);
}
}
}如果 /data/app 扫描后 mPackages 仍没有目标包,PMS 计算原分区对应的 parse/scan flags,启用 system setting 并重新扫描。这个 fallback 同时覆盖 data 文件缺失和解析失败;stub 会加入稍后的解压列表。
6. OTA 删除原包
6.1 disabled 清理
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
mPm.mSettings.removeDisabledSystemPackageLPw(packageName);
if (pkg != null) {
// data APK 仍存在,但不再拥有 system 身份和特权
mRemovePackageHelper.removePackage(pkg, true);
final PackageSetting ps =
mPm.mSettings.getPackageLPr(packageName);
if (ps != null) {
ps.getPkgState().setUpdatedSystemApp(false);
}
try (PackageManagerTracedLock installLock =
mPm.mInstallLock.acquireLock()) {
initPackageTracedLI(new File(pkg.getPath()), 0, scanFlags);
}
}
final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
if (ps != null && mPm.mPackages.get(packageName) == null) {
mRemovePackageHelper.removePackageData(ps, userIds);
}OTA 移除预装包后,data 更新包若仍存在,就重新按普通 data APK 扫描并撤销 updated-system 身份;它不应继续保有系统特权。若 data APK 也不存在或重扫失败,最后删除包数据和残留设置。
6.2 同名包冲突
OTA 还可能首次加入一个与既有 data 包同名的 system APK。签名不兼容时 PMS 删除 data 包;签名兼容且 system 版本更高或 shared UID 改变时 system 胜出并清理 data code;否则隐藏 system 包,稍后继续使用 data 包。这不是 disabled-system 的旧更新场景,但使用同一启动扫描裁决框架。
7. 时序总图
8. 验证方法
8.1 system 版本更高
准备 system v3、data v2 和有效 disabled record,重启扫描。断言 data code path 被清理,mPackages 指向 system v3,updated-system 状态清除。该场景验证 isSystemPkgBetter 的版本分支。
8.2 data 版本更高
准备 system v2、data v3。system 扫描应产生 updated-version-better 内部跳过,data 扫描后 v3 成为 active;disabled setting 应保存本轮 system 签名/路径。内部异常不能被误报为开机失败。
8.3 data 损坏
保留 expectingBetter 条件,但让 data APK 缺失或无法解析。checkExistingBetterPackages 应启用并重扫 system fallback。断言最终系统仍有可查询包,且 stub 包进入后续解压列表。
8.4 原包被删除
从新镜像移除原 system APK,但保留 data 更新。断言 data APK 以普通应用身份重扫,system/updated-system 特权被撤销;若 data 也损坏,则包设置和数据最终清除。
adb shell pm path <package.name>
adb shell dumpsys package <package.name> | grep -E 'versionCode|codePath|updated-system|sharedUser'9. 源码路线
InitAppsHelper.scanSystemApps:确认 system 扫描和初步清理顺序。InstallPackageHelper.addForInitLI:阅读isSystemPkgUpdated、isSystemPkgBetter和签名收集。prepareSystemPackageCleanUp:理解expectingBetter与 deleted-system 分类。InitAppsHelper.initNonSystemApps:确认/data/app在 system 之后扫描。cleanupDisabledPackageSettings:追踪 OTA 删除系统身份后的 data 重扫。checkExistingBetterPackages:追踪 data 缺失时的 system fallback。installSystemStubPackages:继续阅读 fallback stub 的解压时机。
OTA 后系统应用的选择不是一次简单版本比较,而是两阶段扫描加一次恢复收尾:system 扫描先建立候选和 fallback,data 扫描尝试兑现“更好版本”,最后再处理未出现、身份消失或解析失败的包。Settings 保存历史身份,mPackages 暴露当前赢家,mExpectingBetter 连接两个扫描阶段;理解这三个状态容器,才能准确解释 OTA 后为什么某个包继续使用 data 版本、切回 system 版本,或被降为普通应用。
