Skip to content

扫描冲突处理

追踪 Android 17 的同包名、签名、版本、Provider、静态库和 APEX 冲突检测、选择与恢复路径。

基于android-17.0.0_r1
AndroidPackageManagerService包扫描system/data冲突处理Reconcile源码阅读

扫描冲突处理 ​

本文面向已经读过 ScanRequest 与 Result、扫描签名校验 和 静态共享库 的读者。前文分别解释了请求/结果对象、签名详情和静态库版本;本文把这些输入放回冲突裁决现场,追踪 system/data 同名包、替换安装、共享 UID、Provider、静态库和 APEX 内 APK 如何被检测、选择、隐藏、删除或恢复。

Android 17 没有一个叫“处理冲突”的总函数。冲突被分散在不同阶段:assertPackageIsValid() 做重复包与 Provider 的早期检查,scanPackageForInitLI() 决定 system 版本和 data 版本谁更可信,ReconcilePackageUtils.reconcilePackages() 处理替换删除、KeySet、证书和 shared user,commitReconciledScanResultLocked() 才把选择结果写进 live state。只看某一个 if,很容易把 warning、跳过、隐藏和删除混成同一种失败。

读完后,读者应能从冲突现象反查源码入口:判断是“同一批次重复包”还是“已安装包替换”,是 system/data 版本选择还是签名不兼容,是 Provider authority 冲突还是静态库逻辑名冲突;同时指出每条分支的 owner、错误码、数据清理范围和下一次启动的恢复入口。

1. 冲突分层 ​

1.1 冲突类型 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
  • frameworks/base/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
  • frameworks/base/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
冲突检测位置主要输入结果
同批次重复包名scanInstallPackages()scannedPackagesINSTALL_FAILED_DUPLICATE_PACKAGE
已知包路径不符assertPackageIsValid()SCAN_REQUIRE_KNOWN、known pathINSTALL_FAILED_PACKAGE_CHANGED
system/data 同名scanPackageForInitLI()签名、版本、shared user、路径启用、隐藏、删除 data 或跳过 system
替换无法删除旧包reconcilePackages()DeletePackageHelperINSTALL_FAILED_REPLACE_COULDNT_DELETE
旧包签名不兼容verifySignatures()SigningDetails、disabled settingINSTALL_FAILED_UPDATE_INCOMPATIBLE
shared user 不兼容verifySignatures()shared user signing detailsINSTALL_FAILED_SHARED_USER_INCOMPATIBLE
Provider authority 冲突ComponentResolver已注册 providerINSTALL_FAILED_CONFLICTING_PROVIDER
静态库逻辑名冲突InstallPackageHelperstatic library reverse indexINSTALL_FAILED_DUPLICATE_PACKAGE
批次共享库重复ReconcilePackageUtilsincoming library mapinternal error

这些冲突的共同点是“包名不是唯一判断条件”。路径、scan flags、旧 PackageSetting、disabled system setting、签名 lineage、版本和 shared user 都可能改变最终决策。

1.2 状态与所有者 ​

ParsedPackage 只是当前 APK 的候选状态;PackageSetting 是旧状态或待提交副本;mPm.mPackages 是当前可查询的 live package map;mPm.mSettings 保存持久化安装状态。冲突处理的关键是判断哪一个对象可以被修改:

  • data 包替换时,删除动作由 DeletePackageHelper 准备,结果由 ReconciledPackage 携带;
  • system/data 选择时,scanPackageForInitLI() 修改 mPackages、disabled setting 或 shouldHideSystemApp;
  • commit 之前,扫描副本可以被丢弃,原有 live state 仍可能保持不变;
  • commit 之后,失败清理不再等价于“撤销所有内存变化”。

2. 早期检查 ​

2.1 批次重复 ​

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

java
private boolean scanInstallPackages(List<InstallRequest> requests,
        Map<String, Boolean> createdAppId, Map<String, Settings.VersionInfo> versionInfos) {
    final Set<String> scannedPackages = new ArraySet<>(requests.size());
    for (InstallRequest request : requests) {
        final ParsedPackage packageToScan = request.getParsedPackage();
        if (packageToScan == null) {
            request.setError(INSTALL_FAILED_SESSION_INVALID,
                    "Failed to obtain package to scan");
            return false;
        }
        final String packageName = packageToScan.getPackageName();
        final ScanResult scanResult = scanPackageTraced(request.getParsedPackage(),
                request.getParseFlags(), request.getScanFlags(),
                System.currentTimeMillis(), request.getUser(),
                request.getAbiOverride(), request.getInstallSource());
        request.setScanResult(scanResult);
        if (!scannedPackages.add(packageName)) {
            request.setError(PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
                    "Duplicate package " + packageName
                            + " in multi-package install request.");
            return false;
        }
    }
    return true;
}

同一 multi-package request 中,重复包名由 scannedPackages 检测。这个检查发生在每个包已经完成 scanPackageTraced() 并写入 ScanResult 之后,所以“结果对象非空”不能说明整个批次成功;第二次出现同名包会使批次失败。

2.2 已知路径 ​

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

java
if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
    if (mPm.isExpectingBetter(pkg.getPackageName())) {
        Slog.w(TAG, "Relax SCAN_REQUIRE_KNOWN requirement for package "
                + pkg.getPackageName());
    } else {
        PackageSetting known = mPm.mSettings.getPackageLPr(pkg.getPackageName());
        if (known != null) {
            if (!pkg.getPath().equals(known.getPathString())) {
                throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
                        "Application package " + pkg.getPackageName()
                                + " found at " + pkg.getPath()
                                + " but expected at " + known.getPathString() + "; ignoring.");
            }
        } else {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
                    "Application package " + pkg.getPackageName()
                            + " not found; ignoring.");
        }
    }
}

SCAN_REQUIRE_KNOWN 不是普通的“包必须存在”开关,它同时要求 code path 与 Settings 中的 path 一致。mExpectingBetter 是 system package OTA 恢复的例外;否则路径变化返回 INSTALL_FAILED_PACKAGE_CHANGED,未知包返回 INSTALL_FAILED_INVALID_INSTALL_LOCATION。

2.3 Provider 冲突 ​

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

java
// Verify that this new package doesn't have any content providers
// that conflict with existing packages. Only do this for a new install.
if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
    mPm.mComponentResolver.assertProvidersNotDefined(pkg);
}

Provider authority 冲突只在 SCAN_NEW_INSTALL 分支做早期检查,以免更新已安装包时因历史状态重新声明而破坏当前系统。冲突由 ComponentResolver 依据已注册 provider 判断,和包签名或 system/data 版本选择不是同一层。

3. system/data 选择 ​

3.1 版本与路径 ​

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

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 版本只有在“路径确实改变”且“版本更高或 shared user 改变”时才被判定为 isSystemPkgBetter。版本相等或 system 更旧并不会自动替换 data;shared user 改变是一个特殊信号,即使版本较小也可能让 system 版本胜出。

3.2 system 胜出 ​

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

java
if (isSystemPkgBetter) {
    // The version of the application on /system is greater than the version on /data.
    synchronized (mPm.mLock) {
        // just remove the loaded entries from package lists
        mPm.mPackages.remove(pkgSetting.getPackageName());
    }

    logCriticalInfo(Log.WARN,
            "System package updated; name: " + pkgSetting.getPackageName()
                    + "; " + pkgSetting.getVersionCode() + " --> "
                    + parsedPackage.getLongVersionCode());

    mRemovePackageHelper.cleanUpResources(
            pkgSetting.getPackageName(), new File(pkgSetting.getPathString()));
    synchronized (mPm.mLock) {
        mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
    }
}

system 胜出时,PMS 先从 live mPackages 移除旧 data 视图,再清理旧资源,最后启用 system package setting。此时保留应用数据,删除的是旧 code/resource 状态;随后 system APK 继续走扫描和 commit。

3.3 data 胜出 ​

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

java
if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
    // The /system version is less than or equal to the /data version.
    if (!allowUpdatedVersionBetterThanApkInApex() || !isApkInApex) {
        parsedPackage.hideAsFinal();
        throw PackageManagerException.ofInternalError(
                "Package " + parsedPackage.getPackageName()
                        + " at " + parsedPackage.getPath()
                        + " ignored: updated version better than this "
                        + parsedPackage.getLongVersionCode(),
                INTERNAL_ERROR_UPDATED_VERSION_BETTER_THAN_SYSTEM);
    }
}

system APK 版本不优于 data 版本时,通常会 hideAsFinal() 后抛内部错误,让 system scan 结果被跳过,data 版本稍后继续作为更新包使用。APK-in-APEX 有例外条件:较新的 data copy 不应仅因为 APEX 内 APK 版本较旧而让 APEX 安装失败,但 APEX 内 APK 仍会被完整扫描,以保证未来移除 data copy 后不会暴露坏包。

3.4 新 system/data ​

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

java
boolean shouldHideSystemApp = false;
if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
        && !pkgSetting.isSystem()) {
    if (!parsedPackage.getSigningDetails().checkCapability(
            pkgSetting.getSigningDetails(), SigningDetails.CertCapabilities.INSTALLED_DATA)
            && !pkgSetting.getSigningDetails().checkCapability(
            parsedPackage.getSigningDetails(), SigningDetails.CertCapabilities.ROLLBACK)) {
        logCriticalInfo(Log.WARN, "System package signature mismatch; name: "
                + pkgSetting.getPackageName());
        try (PackageFreezer freezer = mPm.freezePackage(
                parsedPackage.getPackageName(), UserHandle.USER_ALL,
                "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER, null)) {
            mDeletePackageHelper.deletePackageLIF(
                    parsedPackage.getPackageName(), null, true,
                    mPm.mUserManager.getUserIds(), 0, new PackageRemovedInfo(), false,
                    Process.SYSTEM_UID);
        }
    } else if (newPkgVersionGreater || newSharedUserSetting) {
        mRemovePackageHelper.cleanUpResources(
                pkgSetting.getPackageName(), new File(pkgSetting.getPathString()));
    } else {
        shouldHideSystemApp = true;
    }
}

新 system 包与非 system data 包同名时分三路:

  • 签名 capability 不匹配:冻结包并删除 data 包;
  • 签名兼容且 system 更新更高或 shared user 改变:清理旧 data 资源,让 system 版本接管;
  • 签名兼容但 system 不更好:设置 shouldHideSystemApp=true,让 data 版本稍后重新扫描。

这里的“隐藏”不是删除 system APK,而是暂时不把它作为 live package;addForInitLI() 结束后会调用 disableSystemPackageLPw() 保存隐藏状态。

4. reconcile 冲突 ​

4.1 替换删除 ​

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

java
final DeletePackageAction deletePackageAction;
// we only want to try to delete for non system apps
if (installRequest.isInstallReplace() && !installRequest.isInstallSystem()) {
    final boolean killApp = (installRequest.getScanFlags() & SCAN_DONT_KILL_APP) == 0;
    final int deleteFlags = PackageManager.DELETE_KEEP_DATA
            | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
    deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(
            installRequest.getRemovedInfo(),
            installRequest.getOriginalPackageSetting(),
            installRequest.getDisabledPackageSetting(),
            deleteFlags, null /* all users */, Process.SYSTEM_UID);
    if (deletePackageAction == null) {
        throw new ReconcileFailure(
                PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
                "May not delete " + installPackageName + " to replace");
    }
} else {
    deletePackageAction = null;
}

普通替换只有在非 system install 时准备删除动作。DELETE_KEEP_DATA 保留用户数据;SCAN_DONT_KILL_APP 决定是否追加不杀进程标志。无法安全删除旧包时,reconcile 直接失败,不会继续签名和 commit。

4.2 KeySet 与证书 ​

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

java
if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, sharedUserSetting, scanFlags)) {
    if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
        if (!isSystemPackage) {
            throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                    "Package " + parsedPackage.getPackageName()
                            + " upgrade keys do not match the previously installed version");
        } else {
            String msg = "System package " + parsedPackage.getPackageName()
                    + " signature changed; retaining data.";
            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
        }
    }
} else {
    final boolean compatMatch = PackageManagerServiceUtils.verifySignatures(
            signatureCheckPs, sharedUserSetting, disabledPkgSetting,
            signingDetails, compareCompat, compareRecover, isRollback);
}

KeySet 检查优先于普通证书比较。非 system 包 KeySet 不匹配直接失败;system 包记录 warning 并保留数据。没有 upgrade keyset 时才进入 verifySignatures(),在那里处理 installed-data、rollback、兼容升级和恢复签名。

4.3 shared user ​

verifySignatures() 在包签名通过后还会检查 shared user 的签名集合和 lineage。若新包不能加入 shared user,返回 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;即使 capability 允许,也必须通过 hasCommonAncestor(),否则 lineage 分叉同样失败。冲突不只属于包自身,而是属于“包 + shared UID 组”的共同状态。

4.4 批次共享库 ​

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

java
for (InstallRequest installRequest : installRequests) {
    installRequest.onReconcileStarted();
    combinedPackages.put(installRequest.getScannedPackageSetting().getPackageName(),
            installRequest.getParsedPackage());

    final List<SharedLibraryInfo> allowedSharedLibInfos =
            sharedLibraries.getAllowedSharedLibInfos(installRequest);
    if (allowedSharedLibInfos != null) {
        for (SharedLibraryInfo info : allowedSharedLibInfos) {
            if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap(
                    incomingSharedLibraries, info)) {
                throw ReconcileFailure.ofInternalError(
                        "Shared Library " + info.getName()
                                + " is being installed twice in this set!",
                        PackageManagerException.INTERNAL_ERROR_SHARED_LIB_INSTALLED_TWICE);
            }
        }
    }
}

同一安装批次的 static library name/version 不能重复声明。这个冲突在 reconcile 第一遍就失败,不会等到全局 mSharedLibraries commit 后才发现;incomingSharedLibraries 是批次级临时索引。

5. 恢复路径 ​

5.1 expecting better ​

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

java
@GuardedBy("mPm.mLock")
public void checkExistingBetterPackages(ArrayMap<String, File> expectingBetterPackages,
        List<String> stubSystemApps, int systemScanFlags, int systemParseFlags) {
    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);
        logCriticalInfo(Log.WARN, "Expected better " + packageName
                + " but never showed up; reverting to system");

        final Pair<Integer, Integer> flags =
                mPm.getSystemPackageRescanFlagsAndReparseFlags(
                        scanFile, systemScanFlags, systemParseFlags);
        if (flags.first == 0) {
            Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
            continue;
        }
        mPm.mSettings.enableSystemPackageLPw(packageName);
        try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
            initPackageTracedLI(scanFile, flags.second, flags.first);
        } catch (PackageManagerException e) {
            Slog.e(TAG, "Failed to parse original system package: " + e.getMessage());
        }
    }
}

如果 system 包被认为“应该有更好的 data 版本”但 data 版本最终没有出现,checkExistingBetterPackages() 会根据原始 code path 反向恢复 scan/reparse flags,重新启用并扫描 system APK。未知 fallback path 被忽略,原始 system APK 解析失败只记录错误,不会伪造成功状态。

5.2 disabled setting ​

system/data 冲突处理中,disabled system package 保存 system 原始版本的签名、路径和 metadata;data 更新版本可能成为 live package。下次扫描 system APK 时,PMS 通过 disabled setting 判断是否仍是 updated system app,并在必要时把原始签名重新收集写回 disabled setting,保证后续 data reconcile 有可信比较对象。

5.3 APEX 例外 ​

APEX 内 APK 的冲突不能简单套 system/data 版本规则:更新 APEX 中的 APK 可能与 system image 或 data copy 同名,allowUpdatedVersionBetterThanApkInApex() 决定较新的 data copy 是否允许压过 APEX 内版本;无论是否最终采用,源码都要求完整扫描 APK-in-APEX,以防将来 data copy 消失后暴露无效包。

6. 测试与诊断 ​

6.1 测试输入 ​

源码文件:frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java

可直接覆盖冲突相关状态的测试包括:

  • system package 设置 FLAG_SYSTEM 后,扫描结果应反映 system 状态;
  • updateSystemApp_applicationInfoFlagSet 注入 existing/disabled setting,断言结果含 FLAG_UPDATED_SYSTEM_APP;
  • installStaticSharedLibrary 断言 synthetic package name 和 static library version;
  • newInstallSimpleAllNominal 与 updateSimpleNominal 对照新设置创建和更新副本;
  • installRealPackageName 验证 original package name 不会被误当成 static library synthetic name。

这些测试证明扫描阶段对象状态,不能单独证明 system/data 删除动作、Provider authority 冲突或完整 commit 恢复。

6.2 日志与错误码 ​

text
adb shell logcat -s PackageManager PackageManagerService ReconcilePackageUtils
adb shell dumpsys package <package-name>
adb shell dumpsys package packages

重点匹配日志:

日志/错误说明
Duplicate package ... in multi-package install request同一批次重复包名
found at ... but expected at ...SCAN_REQUIRE_KNOWN 路径改变
System package signature mismatch新 system 与旧 data 签名 capability 不兼容
System package disableddata 版本更高,system 版本隐藏
System package enabledsystem 版本更高或 shared user 改变
UPDATE_INCOMPATIBLEKeySet/旧包签名/替换关系失败
SHARED_USER_INCOMPATIBLEshared UID 签名或 lineage 冲突
CONFLICTING_PROVIDERProvider authority 已被占用

6.3 决策复盘 ​

遇到同包名冲突时,建议记录一张最小状态表:

字段system/data 对照
code path/system/...、/data/app/... 或 /apex/...
version code新旧版本比较
SigningDetailscurrent signer、lineage、capability
isSystem/isUpdatedSystemAppPackageSetting 和 disabled setting
shared userold/new SharedUserSetting
scan flagsSCAN_AS_SYSTEM、SCAN_NEW_INSTALL、SCAN_AS_APEX
live statemPm.mPackages 是否存在
cleanupdata code path、资源、AppId 是否已清理

只有把这些字段同时对照,才能区分“system 被隐藏等待 data 重扫”和“data 已被删除、system 接管”这两种完全不同的结果。

7. 源码路线 ​

定位扫描冲突时,可以沿以下顺序阅读:

  1. scanInstallPackages():确认批次重复包和扫描结果写入时机。
  2. assertPackageIsValid():确认已知路径、Provider、静态库包名和基本 validity。
  3. scanPackageForInitLI():确认 system/data 版本、路径、签名、shared user 和 APEX 选择。
  4. ReconcilePackageUtils.reconcilePackages():确认替换删除、KeySet、证书和 shared user 的二阶段判断。
  5. PackageManagerServiceUtils.verifySignatures():确认 capability、compat/recover、rollback 和 disabled setting。
  6. SharedLibrariesImpl:确认静态库版本和批次共享库冲突。
  7. commitReconciledScanResultLocked():确认隐藏/删除/更新后的设置何时进入 live state。
  8. checkExistingBetterPackages():确认“更好版本没有出现”时的 system 恢复。

一个实用练习是:设备上已有 /data/app/foo 版本 5,OTA 新增 /system/app/foo 版本 4,签名 lineage 兼容,且 system 版本没有 shared user 改变。请指出 system scan 会设置哪个布尔状态、是否删除 data、何时重新扫描 data,以及如果 data 目录随后消失,哪个恢复方法会重新启用 system 包。

8. 设计收束 ​

Android 17 的扫描冲突处理可以归纳为一条状态裁决链:

  • 早期检查负责阻止同批次重复包、未知路径、Provider authority 和静态库逻辑名冲突;
  • system/data 选择同时依赖签名 capability、版本、路径变化和 shared user,结果可能是删除 data、启用 system、隐藏 system 或跳过当前 system 输入;
  • 普通替换先准备可删除动作,再做 KeySet/证书/shared user reconcile,失败不会继续 commit;
  • ReconciledPackage 和 PackageSetting 副本承载待提交状态,mPm.mPackages 只有在 commit 后才代表最终可查询对象;
  • disabled system setting、mExpectingBetter 和 checkExistingBetterPackages() 为 OTA/更新异常提供恢复路径;
  • APEX 内 APK、static library 和 Provider 各自有额外边界,不能用“包名冲突 = 删除旧包”概括。

后续专题会进入增量扫描,继续分析首次启动、普通启动、fingerprint 变化和 OTA 之后,PMS 如何决定哪些冲突状态需要重扫、复用或清理。