Skip to content

OTA 系统应用更新

解释 OTA 重启扫描时 system 与 data 系统应用版本的裁决、回退和清理路径。

基于android-17.0.0_r1
AndroidPackageManagerServiceOTA系统应用源码阅读

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

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

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 条件 ​

java
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

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

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

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 未出现 ​

java
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

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 也损坏,则包设置和数据最终清除。

bash
adb shell pm path <package.name>
adb shell dumpsys package <package.name> | grep -E 'versionCode|codePath|updated-system|sharedUser'

9. 源码路线 ​

  1. InitAppsHelper.scanSystemApps:确认 system 扫描和初步清理顺序。
  2. InstallPackageHelper.addForInitLI:阅读 isSystemPkgUpdated、isSystemPkgBetter 和签名收集。
  3. prepareSystemPackageCleanUp:理解 expectingBetter 与 deleted-system 分类。
  4. InitAppsHelper.initNonSystemApps:确认 /data/app 在 system 之后扫描。
  5. cleanupDisabledPackageSettings:追踪 OTA 删除系统身份后的 data 重扫。
  6. checkExistingBetterPackages:追踪 data 缺失时的 system fallback。
  7. installSystemStubPackages:继续阅读 fallback stub 的解压时机。

OTA 后系统应用的选择不是一次简单版本比较,而是两阶段扫描加一次恢复收尾:system 扫描先建立候选和 fallback,data 扫描尝试兑现“更好版本”,最后再处理未出现、身份消失或解析失败的包。Settings 保存历史身份,mPackages 暴露当前赢家,mExpectingBetter 连接两个扫描阶段;理解这三个状态容器,才能准确解释 OTA 后为什么某个包继续使用 data 版本、切回 system 版本,或被降为普通应用。