Skip to content

ScanRequest 与 Result

从 ScanRequest 构造追踪到 ScanResult、InstallRequest、reconcile 和 commit,解释扫描状态如何在 PMS 内传递。

基于android-17.0.0_r1
AndroidPackageManagerService包扫描ScanRequestScanResultSharedLibrary源码阅读

ScanRequest 与 ScanResult ​

本文面向已经读过 PMS 目录扫描和分区扫描入口、准备继续追踪“一个包的扫描状态如何流过 PMS”的读者。建议先阅读 系统包扫描、scanDirTracedLI、system与priv-app、data-app扫描 和 vendor与product扫描。这些文章负责解释目录怎样变成 ParsedPackage 和 scan flags;本文继续向后追踪请求对象、结果对象和提交阶段。

这篇文章解决一个具体问题:当 InstallPackageHelper 已经拿到解析后的 ParsedPackage 时,PMS 如何把“旧包、旧设置、disabled system package、shared user、重命名、用户、ABI 覆盖和 flags”装入 ScanRequest,又如何把新 PackageSetting、共享库描述和复制状态装入 ScanResult,最后交给 InstallRequest、ReconcilePackageUtils 和 commit 逻辑。文章不重新讲 APK Manifest 的字段解析,也不把 ScanRequest 当成安装会话的全部参数。

阅读完后,读者应当能够从 scanPackageNew() 进入 prepareInitialScanRequest(),判断某个字段来自 Settings、来自 ParsedPackage 还是来自当前调用参数;能够解释首次扫描、更新扫描和系统包恢复为什么使用不同的 PackageSetting;还能根据测试断言和日志判断结果是在“扫描阶段失败”还是在“reconcile/commit 阶段失败”。

1. 对象边界 ​

1.1 三种包对象 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/ScanRequest.java
  • frameworks/base/services/core/java/com/android/server/pm/ScanResult.java

ParsedPackage、PackageSetting 和 AndroidPackage 在这条链上不是同一层对象:

对象所有者进入时机主要消费者
ParsedPackagePackage parser / 当前扫描流程APK 或 APEX 内 APK 解析结束applyPolicy()、scanPackageOnly()、reconcile
PackageSettingSettings从持久化设置读取,或扫描时新建/复制AppId、用户状态、ABI、路径、commit
AndroidPackagePackageSetting.pkg 的运行时包视图PackageSetting.setPkg() 后mPackages、组件解析、查询快照

ScanRequest 只是把这些对象和扫描上下文放在同一个输入边界;ScanResult 也不是最终的“已注册包”,它只返回扫描阶段生成的设置和共享库信息。只有 commitReconciledScanResultLocked() 完成后,包才会进入 PMS 的 live data structures。

1.2 扫描与提交 ​

ScanPackageUtils.scanPackageOnly() 的注释是“只扫描、不产生副作用”。这里的“副作用”应理解为不直接注册到 PMS 的全局包表,而不是说方法内部完全不改对象:首次扫描会创建 PackageSetting,更新扫描会复制并修改 PackageSetting,ParsedPackage 也会得到 ABI、native library path 和 policy 结果。真正把结果写入 mPm.mPackages、组件解析器和共享库注册表的是后面的 reconcile/commit。

这一区分决定了失败处理:如果 scanPackageOnly() 抛异常,新的设置对象还没有成为 PMS 的 live setting;如果 optimisticallyRegisterAppIds() 已经分配了 AppId,调用方必须执行 cleanUpAppIdCreations();如果已经进入 commitReconciledScanResultLocked(),源码明确承认中途异常可能留下不一致状态,因此 commit 阶段的错误不能和 parse 失败混为一谈。

1.3 调用层级 ​

系统启动路径和普通安装路径都能到达 ScanResult,但外层承接对象不同:

  • 启动扫描:addForInitLI() 调用 scanPackageForInitLI(),再创建 InstallRequest、reconcile 并立即 commit。
  • 普通安装:scanInstallPackages() 对每个 InstallRequest 调用 scanPackageTraced(),把返回结果写回原请求,随后执行重复包、存储属性、AppId 和版本信息检查。
  • 测试路径:ScanTests.executeScan() 直接调用 scanPackageOnly(),测试结束时手动执行 hideAsFinal(),因为测试没有经过 PMS 的完整提交路径。

因此不能只看 ScanRequest 的构造函数判断生命周期;必须继续看谁保存了 ScanResult,以及谁在何时读取它。

2. 请求对象 ​

2.1 字段分组 ​

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

java
/** A package to be scanned */
@VisibleForTesting
final class ScanRequest {
    /** The parsed package */
    @NonNull public final ParsedPackage mParsedPackage;
    /** The package this package replaces */
    @Nullable public final AndroidPackage mOldPkg;
    /** Shared user settings, if the old package has a shared user */
    @Nullable public final SharedUserSetting mOldSharedUserSetting;
    /**
     * Package settings of the currently installed version.
     * <p><em>IMPORTANT:</em> The contents of this object may be modified
     * during scan.
     */
    @Nullable public final PackageSetting mPkgSetting;
    /** Shared user settings of the currently installed package */
    @Nullable public final SharedUserSetting mSharedUserSetting;
    /** A copy of the settings for the currently installed version */
    @Nullable public final PackageSetting mOldPkgSetting;
    /** Package settings for the disabled version on the /system partition */
    @Nullable public final PackageSetting mDisabledPkgSetting;
    /** Package settings for the installed version under its original package name */
    @Nullable public final PackageSetting mOriginalPkgSetting;
    /** The real package name of a renamed application */
    @Nullable public final String mRealPkgName;
    public final @ParsingPackageUtils.ParseFlags int mParseFlags;
    public final @PackageManagerService.ScanFlags int mScanFlags;
    /** The user for which the package is being scanned */
    @Nullable public final UserHandle mUser;
    /** Whether or not the platform package is being scanned */
    public final boolean mIsPlatformPackage;
    /** Override value for package ABI if set during install */
    @Nullable public final String mCpuAbiOverride;
    public final boolean mEnableAlignmentChecks;

字段可以按责任分为四组:

  1. mParsedPackage 是本次输入;mOldPkg 是被替换包的运行时视图。
  2. mPkgSetting、mDisabledPkgSetting、mOriginalPkgSetting 和 mOldPkgSetting 描述当前、disabled、原始包名和扫描前副本。
  3. mOldSharedUserSetting 与 mSharedUserSetting 用来比较 shared UID 是否改变,并把当前包接入正确的共享用户。
  4. parse/scan flags、用户、platform package、ABI override 和 16 KB alignment 开关控制本次扫描的条件路径。

最容易误读的是 mPkgSetting 与 mOldPkgSetting:前者是当前已安装设置的引用,类注释明确允许扫描过程中修改它;后者在构造函数中复制出来,用于保存扫描前状态,供更新、签名、路径和 commit 逻辑回溯。

2.2 构造与复制 ​

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

java
ScanRequest(
        @NonNull ParsedPackage parsedPackage,
        @Nullable SharedUserSetting oldSharedUserSetting,
        @Nullable AndroidPackage oldPkg,
        @Nullable PackageSetting pkgSetting,
        @Nullable SharedUserSetting sharedUserSetting,
        @Nullable PackageSetting disabledPkgSetting,
        @Nullable PackageSetting originalPkgSetting,
        @Nullable String realPkgName,
        @ParsingPackageUtils.ParseFlags int parseFlags,
        @PackageManagerService.ScanFlags int scanFlags,
        boolean isPlatformPackage,
        @Nullable UserHandle user,
        @Nullable String cpuAbiOverride,
        boolean enableAlignmentChecks) {
    mParsedPackage = parsedPackage;
    mOldPkg = oldPkg;
    mPkgSetting = pkgSetting;
    mOldSharedUserSetting = oldSharedUserSetting;
    mSharedUserSetting = sharedUserSetting;
    mOldPkgSetting = pkgSetting == null ? null : new PackageSetting(pkgSetting);
    mDisabledPkgSetting = disabledPkgSetting;
    mOriginalPkgSetting = originalPkgSetting;
    mRealPkgName = realPkgName;
    mParseFlags = parseFlags;
    mScanFlags = scanFlags;
    mIsPlatformPackage = isPlatformPackage;
    mUser = user;
    mCpuAbiOverride = cpuAbiOverride;
    mEnableAlignmentChecks = enableAlignmentChecks;
}

这里有三个重要的不变量:

  • mParsedPackage 永远非空,否则没有扫描输入;其他历史设置允许为空,因为首次安装没有旧包。
  • mOldPkgSetting 只在 pkgSetting 非空时复制;首次安装没有“扫描前设置”,因此为 null。
  • 复制是 new PackageSetting(pkgSetting),而不是把同一个引用再存一份。PackageSetting 源码说明普通复制会复制设置数据,但保留同一个 PackageParser.Package 实例;所以它保护的是设置字段的更新,不是把 ParsedPackage 做深复制。

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

java
/**
 * New instance of PackageSetting replicating the original settings.
 * Note that it keeps the same PackageParser.Package instance.
 */
PackageSetting(PackageSetting orig) {
    this(orig, false);
}

/** Updates all fields in the current setting from another. */
public void updateFrom(PackageSetting other) {
    copyPackageSetting(other, false /* sealedSnapshot */);

    Set<String> mimeGroupNames = other.mimeGroups != null ? other.mimeGroups.keySet() : null;
    updateMimeGroups(mimeGroupNames);

    onChanged();
}

因此 mOldPkgSetting 的用途是“扫描前的设置基线”,并不意味着所有后续对象都不可变。更新扫描中,scanPackageOnly() 还会再次复制 mPkgSetting,把修改写入新的结果设置;启动更新分支则可能把结果反向 updateFrom() 回 disabled system setting。读代码时必须记录每一次复制和回写的方向。

2.3 初始请求 ​

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

java
private ScanRequest prepareInitialScanRequest(@NonNull ParsedPackage parsedPackage,
        @ParsingPackageUtils.ParseFlags int parseFlags,
        @PackageManagerService.ScanFlags int scanFlags,
        @Nullable UserHandle user, String cpuAbiOverride, @Nullable InstallSource installSource)
        throws PackageManagerException {
    final AndroidPackage platformPackage;
    final String realPkgName;
    final PackageSetting disabledPkgSetting;
    final PackageSetting installedPkgSetting;
    final PackageSetting originalPkgSetting;
    final SharedUserSetting sharedUserSetting;
    SharedUserSetting oldSharedUserSetting = null;

    final boolean isSystemApp = AndroidPackageLegacyUtils.isSystem(parsedPackage);
    synchronized (mPm.mLock) {
        platformPackage = mPm.getPlatformPackage();
        final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
                AndroidPackageUtils.getRealPackageOrNull(parsedPackage, isSystemApp));
        realPkgName = ScanPackageUtils.getRealPackageName(parsedPackage, renamedPkgName,
                isSystemApp);
        if (realPkgName != null) {
            ScanPackageUtils.ensurePackageRenamed(parsedPackage, renamedPkgName);
        }
        originalPkgSetting = getOriginalPackageLocked(parsedPackage, renamedPkgName);
        installedPkgSetting = mPm.mSettings.getPackageLPr(parsedPackage.getPackageName());
        disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(
                parsedPackage.getPackageName());

        boolean ignoreSharedUserId = false;
        if (installedPkgSetting == null || !installedPkgSetting.hasSharedUser()) {
            ignoreSharedUserId = parsedPackage.isLeavingSharedUser();
        }

        if (!ignoreSharedUserId && parsedPackage.getSharedUserId() != null) {
            sharedUserSetting = mPm.mSettings.getSharedUserLPw(
                    parsedPackage.getSharedUserId(),
                    0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
        } else {
            sharedUserSetting = null;
        }
        if (installedPkgSetting != null) {
            oldSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(installedPkgSetting);
        }
    }

    final boolean isPlatformPackage = platformPackage != null
            && platformPackage.getPackageName().equals(parsedPackage.getPackageName());
    final String initiatingPackage = installSource != null
            ? installSource.mInitiatingPackageName : null;
    final boolean enableAlignmentChecks = ScanPackageUtils.enableAlignmentChecks(
            parsedPackage, mPm.mInjector.getContext(), initiatingPackage,
            isSystemApp, isPlatformPackage, scanFlags);
    return new ScanRequest(parsedPackage, oldSharedUserSetting,
            installedPkgSetting == null ? null : installedPkgSetting.getPkg(),
            installedPkgSetting, sharedUserSetting, disabledPkgSetting,
            originalPkgSetting, realPkgName, parseFlags, scanFlags, isPlatformPackage,
            user, cpuAbiOverride, enableAlignmentChecks);
}

这段代码展示了请求字段的真实来源:

  • mPkgSetting 来自 Settings.getPackageLPr();mOldPkg 则从这个设置的 getPkg() 取出。
  • mDisabledPkgSetting 来自 disabled system package 表,不等于普通的已安装设置。
  • mOriginalPkgSetting 和 mRealPkgName 只在重命名链存在时有值。
  • mSharedUserSetting 可能在 mLock 内按 Manifest 的 shared user ID 创建;mOldSharedUserSetting 来自旧设置,用于比较迁移前后。
  • mEnableAlignmentChecks 在离开 mLock 后计算,源码注释说明这样做是为了避免调用 SettingsProvider 形成死锁。

这个顺序也说明了为什么不能从 ParsedPackage.isSystem() 直接推断最终扫描 policy:isSystemApp 只是构造初始请求时的判断,稍后 adjustScanFlags() 和 applyPolicy() 还会结合 disabled setting、分区 flags 和 updated system app 状态重新计算。

2.4 两次构造 ​

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

java
private ScanResult scanPackageNew(@NonNull ParsedPackage parsedPackage,
        final @ParsingPackageUtils.ParseFlags int parseFlags,
        @PackageManagerService.ScanFlags int scanFlags, long currentTime,
        @Nullable UserHandle user, String cpuAbiOverride, @Nullable InstallSource installSource)
        throws PackageManagerException {
    final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags,
            scanFlags, user, cpuAbiOverride, installSource);
    final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting;
    final PackageSetting disabledPkgSetting = initialScanRequest.mDisabledPkgSetting;

    final boolean isUpdatedSystemApp = installedPkgSetting != null
            ? installedPkgSetting.isUpdatedSystemApp() : disabledPkgSetting != null;
    final int newScanFlags = adjustScanFlags(scanFlags, installedPkgSetting,
            disabledPkgSetting, user, parsedPackage);
    ScanPackageUtils.applyPolicy(parsedPackage, newScanFlags,
            mPm.getPlatformPackage(), isUpdatedSystemApp);

    synchronized (mPm.mLock) {
        assertPackageIsValid(parsedPackage, parseFlags, newScanFlags);
        final ScanRequest request = new ScanRequest(parsedPackage,
                initialScanRequest.mOldSharedUserSetting,
                initialScanRequest.mOldPkg, installedPkgSetting,
                initialScanRequest.mSharedUserSetting, disabledPkgSetting,
                initialScanRequest.mOriginalPkgSetting, initialScanRequest.mRealPkgName,
                parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user,
                cpuAbiOverride, initialScanRequest.mEnableAlignmentChecks);
        return ScanPackageUtils.scanPackageOnly(request, mPm.mInjector, mPm.mFactoryTest,
                currentTime);
    }
}

这里不是“同一个请求被重复初始化”,而是有两个不同用途的请求:

  1. initialScanRequest 在读取 Settings 和计算 alignment 开关时作为上下文容器。
  2. policy 调整后,在 mLock 内重新构造真正传给 scanPackageOnly() 的请求,把 newScanFlags 只用于 applyPolicy(),而把原始 scanFlags 放入请求。

这个细节意味着 ParsedPackage 看到的 policy flags 可能是调整后的,而 ScanRequest.mScanFlags 保存的是调用层传入的 flags。scanPackageOnly() 再根据 PackageSetting 和这些输入派生状态。调试时若只打印 mScanFlags,不能假设它等于 applyPolicy() 使用的 newScanFlags。

3. 扫描实现 ​

3.1 设置分叉 ​

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

java
public static ScanResult scanPackageOnly(@NonNull ScanRequest request,
        PackageManagerServiceInjector injector,
        boolean isUnderFactoryTest, long currentTime)
        throws PackageManagerException {
    final PackageAbiHelper packageAbiHelper = injector.getAbiHelper();
    ParsedPackage parsedPackage = request.mParsedPackage;
    PackageSetting pkgSetting = request.mPkgSetting;
    final PackageSetting disabledPkgSetting = request.mDisabledPkgSetting;
    final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
    final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
    final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
    final String realPkgName = request.mRealPkgName;
    final SharedUserSetting oldSharedUserSetting = request.mOldSharedUserSetting;
    final SharedUserSetting sharedUserSetting = request.mSharedUserSetting;
    final UserHandle user = request.mUser;
    final boolean isPlatformPackage = request.mIsPlatformPackage;

    final File destCodeFile = new File(parsedPackage.getPath());
    boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
    boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;

方法首先把请求字段拆成局部变量,之后的代码主要围绕 pkgSetting 是否存在分叉。它不会重新从磁盘寻找包,也不会重新决定目录属于哪个 partition;这些工作已经由前面的解析和 scan flags 完成。

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

java
boolean isPendingRestoreBefore = false;
String[] usesSdkLibraries = null;
if (!parsedPackage.getUsesSdkLibraries().isEmpty()) {
    usesSdkLibraries = new String[parsedPackage.getUsesSdkLibraries().size()];
    parsedPackage.getUsesSdkLibraries().toArray(usesSdkLibraries);
}
String[] usesStaticLibraries = null;
if (!parsedPackage.getUsesStaticLibraries().isEmpty()) {
    usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
    parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
}
final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();

if (pkgSetting != null && oldSharedUserSetting != sharedUserSetting) {
    PackageManagerService.reportSettingsProblem(Log.WARN,
            "Package " + parsedPackage.getPackageName() + " shared user changed from "
                    + (oldSharedUserSetting != null
                    ? oldSharedUserSetting.name : "<nothing>")
                    + " to "
                    + (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>")
                    + "; replacing with new");
    isPendingRestoreBefore = pkgSetting.isPendingRestore();
    pkgSetting = null;
}

final boolean createNewPackage = (pkgSetting == null);
if (createNewPackage) {
    final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
    final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;
    final boolean isStoppedSystemApp = (scanFlags & SCAN_AS_STOPPED_SYSTEM_APP) != 0;
    int pkgFlags = PackageInfoUtils.appInfoFlags(parsedPackage, null);
    int pkgPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(parsedPackage, null);

    pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName(),
            originalPkgSetting, disabledPkgSetting, realPkgName, sharedUserSetting,
            destCodeFile, parsedPackage.getNativeLibraryRootDir(),
            AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
            AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
            parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
            true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp,
            UserManagerService.getInstance(),
            usesSdkLibraries,
            parsedPackage.getUsesSdkLibrariesVersionsMajor(),
            parsedPackage.getUsesSdkLibrariesOptional(),
            usesStaticLibraries,
            parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
            newDomainSetId, parsedPackage.getTargetSdkVersion(),
            parsedPackage.getRestrictUpdateHash());
} else {
    pkgSetting = new PackageSetting(pkgSetting);
    pkgSetting.setPkg(parsedPackage);
    Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, oldSharedUserSetting,
            sharedUserSetting, destCodeFile, parsedPackage.getNativeLibraryDir(),
            pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi(),
            PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
            PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
            UserManagerService.getInstance(),
            usesSdkLibraries,
            parsedPackage.getUsesSdkLibrariesVersionsMajor(),
            parsedPackage.getUsesSdkLibrariesOptional(),
            usesStaticLibraries,
            parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
            newDomainSetId, parsedPackage.getTargetSdkVersion(),
            parsedPackage.getRestrictUpdateHash(),
            (scanFlags & SCAN_DONT_KILL_APP) != 0);
}

上面的源码片段按真实 Android 17 逻辑压缩了数组转换,但分叉关系不变:

  • shared user 改变时,旧 pkgSetting 不继续复用,而是先保存 isPendingRestore,然后置空,迫使后续走“创建新设置”分支。
  • 首次安装使用 Settings.createNewSetting(),并把 instant、virtual preload、stopped system app、共享库依赖和 domain verification ID 一起写入新设置。
  • 更新扫描先 new PackageSetting(pkgSetting),再在副本上设置 ParsedPackage 和更新字段,保护调用方仍持有的旧设置。
  • mDisabledPkgSetting 和 mOriginalPkgSetting 只在创建新设置时参与包名/系统包关系;更新分支通过 updatePackageSetting() 把它们作为比较上下文传入。

源码中的 createNewPackage 是一个局部状态,不是“这个包从来没有安装过”的全局结论。系统包恢复、shared user 改变和原始包名切换,都可能让一个曾经存在的包在本次扫描中重新创建 PackageSetting。

3.2 ABI 与路径 ​

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

java
String primaryCpuAbiFromSettings = null;
String secondaryCpuAbiFromSettings = null;
boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;

if (!needToDeriveAbi) {
    if (pkgSetting != null) {
        if (pkgSetting.getPkg() != null && pkgSetting.getPkg().isStub()) {
            needToDeriveAbi = true;
        } else {
            primaryCpuAbiFromSettings = pkgSetting.getPrimaryCpuAbiLegacy();
            secondaryCpuAbiFromSettings = pkgSetting.getSecondaryCpuAbiLegacy();
        }
    } else {
        // Re-scanning a system package after uninstalling updates; need to derive ABI
        needToDeriveAbi = true;
    }
}

ABI 处理不是 ScanResult 的独立字段,而是扫描期间同时写入 ParsedPackage 和结果 PackageSetting 的派生状态:

  • 首次启动或升级通常重新 derive ABI;stub package 也强制 derive。
  • 普通重扫描优先复用旧设置中的 primary/secondary ABI。
  • 没有旧设置的系统包恢复需要 derive,而 APEX payload 的 native library 又有单独路径规则。
  • SCAN_NEW_INSTALL、SCAN_MOVE、SCAN_NO_DEX 会改变 native library path 和 ABI override 的条件。

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

java
pkgSetting.setPrimaryCpuAbi(AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage))
        .setSecondaryCpuAbi(AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage))
        .setCpuAbiOverride(cpuAbiOverride);

// Push the derived path down into PackageSettings so we know what to
// clean up at uninstall time.
pkgSetting.setLegacyNativeLibraryPath(parsedPackage.getNativeLibraryRootDir());

这两次写入说明结果设置不仅用于查询。legacyNativeLibraryPath 会成为卸载清理的输入;如果只看 ApplicationInfo.nativeLibraryDir,会遗漏 Settings 侧真正保存、供清理逻辑读取的路径。

3.3 时间与 policy ​

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

java
pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(pkgSetting, parsedPackage,
        sharedUserSetting, injector.getCompatibility()));

if (pkgSetting.isSystem()) {
    configurePackageComponents(parsedPackage);
}

final long scanFileTime = getLastModifiedTime(parsedPackage);
final long earliestFirstInstallTime =
        PackageStateUtils.getEarliestFirstInstallTime(pkgSetting.getUserStates());
if (currentTime != 0) {
    if (earliestFirstInstallTime == 0) {
        pkgSetting.setFirstInstallTime(currentTime, userId)
                .setLastUpdateTime(currentTime);
    } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
        pkgSetting.setLastUpdateTime(currentTime);
    }
} else if (earliestFirstInstallTime == 0) {
    pkgSetting.setFirstInstallTime(scanFileTime, userId)
            .setLastUpdateTime(scanFileTime);
} else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) {
    if (scanFileTime != pkgSetting.getLastModifiedTime()) {
        pkgSetting.setLastUpdateTime(scanFileTime);
    }
}
pkgSetting.setLastModifiedTime(scanFileTime);

currentTime 的值由调用方决定:普通安装传入当前时间;初始化扫描使用当前时间;测试可以传入特定值。传入 0 时,源码退回 APK 文件修改时间。这里的时间状态写入发生在 ScanResult 返回前,因此 reconcile 看到的是已经更新过的 PackageSetting,但持久化到 packages XML 仍要等 commit/update settings 阶段。

3.4 共享库结果 ​

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

java
SharedLibraryInfo sdkLibraryInfo = null;
if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
    sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
}
SharedLibraryInfo staticSharedLibraryInfo = null;
if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibraryName())) {
    staticSharedLibraryInfo =
            AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
}
List<SharedLibraryInfo> dynamicSharedLibraryInfos = null;
if (!ArrayUtils.isEmpty(parsedPackage.getLibraryNames())) {
    dynamicSharedLibraryInfos = new ArrayList<>(parsedPackage.getLibraryNames().size());
    for (String name : parsedPackage.getLibraryNames()) {
        dynamicSharedLibraryInfos.add(
                AndroidPackageUtils.createSharedLibraryForDynamic(parsedPackage, name));
    }
}

return new ScanResult(request, pkgSetting, !createNewPackage,
        Process.INVALID_UID, sdkLibraryInfo, staticSharedLibraryInfo,
        dynamicSharedLibraryInfos);

一个 ParsedPackage 最多同时产生三种共享库描述:SDK shared library 一个、static shared library 一个、dynamic shared libraries 一个列表。它们只是“声明本包提供什么库”的扫描结果;依赖解析和冲突检查在 ReconcilePackageUtils 与 SharedLibrariesImpl 中继续完成。

null 和空列表也有语义差异:没有 dynamic library 声明时,mDynamicSharedLibraryInfos 为 null;有声明时列表顺序与 Manifest 解析出的 library names 顺序一致。调用方通过 InstallRequest.getDynamicSharedLibraryInfos() 读取前必须先经过 assertScanResultExists(),不能在扫描完成前访问。

4. 结果对象 ​

4.1 字段含义 ​

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

java
/** The result of a package scan. */
@VisibleForTesting
final class ScanResult {
    /** The request that initiated the scan that produced this result. */
    @NonNull public final ScanRequest mRequest;
    /** Whether or not the original PackageSetting needs to be updated with this result on commit. */
    public final boolean mExistingSettingCopied;
    /**
     * The previous app ID if the app decided to leave a shared user ID.
     * The value is set *only* if the app is leaving a shared user ID.
     * Default value is Process.INVALID_UID.
     */
    public final int mPreviousAppId;
    /** The final package settings. */
    @Nullable public final PackageSetting mPkgSetting;
    public final SharedLibraryInfo mSdkSharedLibraryInfo;
    public final SharedLibraryInfo mStaticSharedLibraryInfo;
    public final List<SharedLibraryInfo> mDynamicSharedLibraryInfos;

结果字段可以分成“回溯输入”“设置结果”“可供注册的库”三类:

  • mRequest 保留完整输入,故 commit 阶段可以通过 mRequest.mOldPkg、mRequest.mOriginalPkgSetting 等 getter 取回历史上下文。
  • mPkgSetting 是最终扫描设置。首次安装是新建对象;更新扫描通常是副本;它还未必已经注册到 Settings.mPackages。
  • mExistingSettingCopied 告诉 commit 是否需要把结果更新回原始设置对象。
  • mPreviousAppId 设计上服务于离开 shared user 的 AppId 迁移,但 Android 17 构造函数把它固定为 INVALID_UID,不能根据字段注释假定迁移分支当前可用。
  • 三组 SharedLibraryInfo 把库声明带到 reconcile;它们不是 PackageSetting 的替代品。

4.2 构造与 AppId ​

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

java
ScanResult(
        @NonNull ScanRequest request,
        @Nullable PackageSetting pkgSetting,
        boolean existingSettingCopied,
        int previousAppId,
        SharedLibraryInfo sdkSharedLibraryInfo,
        SharedLibraryInfo staticSharedLibraryInfo,
        List<SharedLibraryInfo> dynamicSharedLibraryInfos) {
    mRequest = request;
    mPkgSetting = pkgSetting;
    mExistingSettingCopied = existingSettingCopied;
    // Hardcode mPreviousAppId to INVALID_UID (http://b/221088088)
    // This will disable all migration code paths in PMS and PermMS
    mPreviousAppId = Process.INVALID_UID;
    mSdkSharedLibraryInfo = sdkSharedLibraryInfo;
    mStaticSharedLibraryInfo = staticSharedLibraryInfo;
    mDynamicSharedLibraryInfos = dynamicSharedLibraryInfos;
}

public boolean needsNewAppId() {
    return mPreviousAppId != Process.INVALID_UID;
}

构造函数虽然接收 previousAppId 参数,但没有使用它,而是硬编码 Process.INVALID_UID。这是 Android 17 的真实行为,意味着 needsNewAppId() 当前始终返回 false。因此 InstallPackageHelper.optimisticallyRegisterAppIds() 的 needsNewAppId() 分支在普通扫描中不会因为 ScanResult 提供旧 AppId 而触发;不要把旧版文章中关于 shared user 离开迁移的描述直接套到这个版本。

4.3 复制标志 ​

源码文件:

  • frameworks/base/services/core/java/com/android/server/pm/ScanPackageUtils.java
  • frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
java
return new ScanResult(request, pkgSetting, !createNewPackage,
        Process.INVALID_UID /* previousAppId */, sdkLibraryInfo, staticSharedLibraryInfo,
        dynamicSharedLibraryInfos);

mExistingSettingCopied = !createNewPackage 的含义是:

  • 首次创建设置时为 false,结果对象只携带新设置。
  • 已有设置时为 true,scanPackageOnly() 已经基于旧设置创建副本,commit 可以在确认成功后把副本的字段合并回原始设置。

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

java
if (scanResult.mExistingSettingCopied
        && scanResult.mRequest.mPkgSetting != null) {
    scanResult.mRequest.mPkgSetting.updateFrom(scanResult.mPkgSetting);
}

这段回写只出现在初始化扫描中、扫描 disabled system package 的特殊分支。普通更新路径并不是拿这个片段直接提交,而是将 ScanResult 包进 InstallRequest,经过 reconcile 后由 commitReconciledScanResultLocked() 决定何时更新正式设置。mExistingSettingCopied 是提交策略的输入,不是“扫描已经成功注册”的标志。

4.4 共享库消费者 ​

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

java
@Nullable
public SharedLibraryInfo getSdkSharedLibraryInfo() {
    assertScanResultExists();
    return mScanResult.mSdkSharedLibraryInfo;
}

@Nullable
public SharedLibraryInfo getStaticSharedLibraryInfo() {
    assertScanResultExists();
    return mScanResult.mStaticSharedLibraryInfo;
}

@Nullable
public List<SharedLibraryInfo> getDynamicSharedLibraryInfos() {
    assertScanResultExists();
    return mScanResult.mDynamicSharedLibraryInfos;
}

public void updateAllCodePaths(List<String> paths) {
    if (mScanResult.mSdkSharedLibraryInfo != null) {
        mScanResult.mSdkSharedLibraryInfo.setAllCodePaths(paths);
    }
    if (mScanResult.mStaticSharedLibraryInfo != null) {
        mScanResult.mStaticSharedLibraryInfo.setAllCodePaths(paths);
    }
    if (mScanResult.mDynamicSharedLibraryInfos != null) {
        for (SharedLibraryInfo info : mScanResult.mDynamicSharedLibraryInfos) {
            info.setAllCodePaths(paths);
        }
    }
}

共享库对象在扫描结果中先以解析阶段的 code paths 存在;安装流程如果最终路径发生变化,可以通过 updateAllCodePaths() 统一修正三类库对象。这个消费者关系解释了为什么 ScanResult 要携带库对象,而不是只在 ParsedPackage 上保留声明:reconcile 和 commit 需要对“将要注册的库”执行版本、路径和冲突处理。

5. 结果流转 ​

5.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;
        }
        request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
        final String packageName = packageToScan.getPackageName();
        try {
            request.onScanStarted();
            final ScanResult scanResult = scanPackageTraced(request.getParsedPackage(),
                    request.getParseFlags(), request.getScanFlags(),
                    System.currentTimeMillis(), request.getUser(),
                    request.getAbiOverride(), request.getInstallSource());
            request.setScanResult(scanResult);
            request.onScanFinished();
            if (!scannedPackages.add(packageName)) {
                request.setError(PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
                        "Duplicate package " + packageName
                                + " in multi-package install request.");
                return false;
            }

普通安装中,InstallRequest 是 ScanResult 的直接 owner:它在扫描前已经存在,扫描返回后才通过 setScanResult() 填入结果。写入顺序很关键:onScanFinished() 发生在重复包检查之前;如果同一个多包请求中第二次出现相同包名,结果已经产生,但整个扫描批次仍以 INSTALL_FAILED_DUPLICATE_PACKAGE 失败。

随后,普通安装还会检查 pending install 集合、noAppDataStorage 一致性、APEX/SDK library 的 AppId 规则和版本信息。也就是说,得到 ScanResult 不等于安装请求已成功。

5.2 reconcile 两遍 ​

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

java
public static List<ReconciledPackage> reconcilePackages(
        List<InstallRequest> installRequests,
        Map<String, AndroidPackage> allPackages,
        Map<String, Settings.VersionInfo> versionInfos,
        SharedLibrariesImpl sharedLibraries,
        KeySetManagerService ksms, Settings settings, SystemConfig systemConfig)
        throws ReconcileFailure {
    final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
    final ArrayMap<String, AndroidPackage> combinedPackages =
            new ArrayMap<>(allPackages.size() + installRequests.size());
    combinedPackages.putAll(allPackages);
    final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
            new ArrayMap<>();

    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);
                }
            }
        }
    }

第一遍构造 combinedPackages,把待安装包覆盖到现有包集合中,同时收集本批次声明的共享库。如果同一个批次声明同名同版本共享库,立即抛出 INTERNAL_ERROR_SHARED_LIB_INSTALLED_TWICE;此时还没有进入 commit。

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

java
for (InstallRequest installRequest : installRequests) {
    final String installPackageName = installRequest.getParsedPackage().getPackageName();
    final List<SharedLibraryInfo> allowedSharedLibInfos =
            sharedLibraries.getAllowedSharedLibInfos(installRequest);
    final DeletePackageAction deletePackageAction;
    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;
    }

    final PackageSetting signatureCheckPs = installRequest.getStaticSharedLibraryInfo() != null
            ? sharedLibraries.getStaticSharedLibLatestVersionSetting(installRequest)
            : installRequest.getScannedPackageSetting();

第二遍才做替换删除、签名/KeySet 检查、shared user 签名合并和共享库路径收集。ScanResult 提供的 mPkgSetting 在这里通过 InstallRequest.getScannedPackageSetting() 成为签名、删除和版本检查的输入;如果这里失败,调用方得到的是 ReconcileFailure,而不是 ScanPackageUtils 的 parse/scan 异常。

5.3 AppId 注册 ​

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

java
private boolean optimisticallyRegisterAppIds(@NonNull InstallRequest installRequest)
        throws PackageManagerException {
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "optimisticallyRegisterAppIds");
    try {
        boolean created = false;
        final PackageSetting ps = installRequest.getScannedPackageSetting();
        if (!installRequest.isExistingSettingCopied() || installRequest.needsNewAppId()) {
            synchronized (mPm.mLock) {
                created |= mPm.mSettings.registerAppIdLPw(ps,
                        installRequest.needsNewAppId());
            }
        }
        synchronized (mPm.mLock) {
            created |= mPm.mSettings.registerPccIdLPw(ps);
        }
        return created;
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}

private void cleanUpAppIdCreations(@NonNull InstallRequest installRequest) {
    PackageSetting pkgSetting = installRequest.getScannedPackageSetting();
    if (pkgSetting != null) {
        int appId = pkgSetting.getAppId();
        int pccId = pkgSetting.getPccId();
        synchronized (mPm.mLock) {
            if (appId > 0) {
                mPm.mSettings.removeAppIdLPw(appId);
            }
            if (pccId > 0) {
                mPm.mSettings.removePccIdLPw(pccId);
            }
        }
    }
}

AppId 分配发生在 reconcile 之后、最终 commit 之前。mExistingSettingCopied 为 false 时,通常需要为新设置注册 AppId;needsNewAppId() 只有在 mPreviousAppId != INVALID_UID 时才为 true,而 Android 17 的 ScanResult 已将其固定为 invalid。因此本版本的普通扫描不会从 ScanResult 触发 shared user 离开迁移 AppId。

如果后续步骤失败,addForInitLI() 会根据 optimisticallyRegisterAppIds() 的返回值调用清理方法。清理只移除本次新建的 AppId/PCC ID,不会删除 APK,也不会回滚已经完成的所有 commit 变更;它的边界是“撤销乐观注册”。

5.4 最终提交 ​

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

java
private AndroidPackage commitReconciledScanResultLocked(
        @NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
    final InstallRequest request = reconciledPkg.mInstallRequest;
    ParsedPackage parsedPackage = request.getParsedPackage();
    final @PackageManagerService.ScanFlags int scanFlags = request.getScanFlags();
    final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
    final PackageSetting originalPkgSetting = request.getScanRequestOriginalPackageSetting();
    final String realPkgName = request.getRealPackageName();
    final PackageSetting pkgSetting;

    if (request.getScanRequestPackageSetting() != null) {
        SharedUserSetting requestSharedUserSetting = mPm.mSettings
                .getSharedUserSettingLPr(request.getScanRequestPackageSetting());
        SharedUserSetting resultSharedUserSetting = mPm.mSettings
                .getSharedUserSettingLPr(request.getScannedPackageSetting());
        if (requestSharedUserSetting != null
                && requestSharedUserSetting != resultSharedUserSetting) {
            requestSharedUserSetting.removePackage(request.getScanRequestPackageSetting());
            if (mPm.mSettings.checkAndPruneSharedUserLPw(requestSharedUserSetting, false)) {
                request.setRemovedAppId(requestSharedUserSetting.mAppId);
            }
        }
    }
    if (request.isExistingSettingCopied()) {
        pkgSetting = request.getScanRequestPackageSetting();
        pkgSetting.updateFrom(request.getScannedPackageSetting());
    } else {
        pkgSetting = request.getScannedPackageSetting();
    }

commit 阶段才把扫描副本合并到正式 PackageSetting,并处理旧 shared user 的移除和裁剪。这里的 request.getScanRequestPackageSetting() 指向 ScanRequest.mPkgSetting,而 request.getScannedPackageSetting() 指向 ScanResult.mPkgSetting;两者在更新扫描时通常不是同一个对象。

随后源码会设置 signing details、调用 commitPackageSettings(),把包写入 PMS 的运行时结构,并处理 instant app、共享库、组件和广播等后续状态。ScanResult 的生命周期在此处结束,但它携带的 PackageSetting 数据已经转化为可查询、可持久化的 PMS 状态。

6. 测试证据 ​

6.1 测试构造器 ​

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

java
class ScanRequestBuilder {
    private final ParsedPackage mPkg;
    private AndroidPackage mOldPkg;
    private SharedUserSetting mOldSharedUserSetting;
    private SharedUserSetting mSharedUserSetting;
    private PackageSetting mPkgSetting;
    private PackageSetting mDisabledPkgSetting;
    private PackageSetting mOriginalPkgSetting;
    private String mRealPkgName;
    private int mParseFlags;
    private int mScanFlags;
    private UserHandle mUser;
    private boolean mIsPlatformPackage;
    private String mCpuAbiOverride;

    ScanRequestBuilder(ParsedPackage pkg) {
        this.mPkg = pkg;
    }

    public ScanRequestBuilder addScanFlag(int scanFlag) {
        this.mScanFlags |= scanFlag;
        return this;
    }

    ScanRequest build() {
        return new ScanRequest(
                mPkg, mOldSharedUserSetting, mOldPkg, mPkgSetting, mSharedUserSetting,
                mDisabledPkgSetting, mOriginalPkgSetting, mRealPkgName, mParseFlags, mScanFlags,
                mIsPlatformPackage, mUser, mCpuAbiOverride, /* enableAlignmentChecks= */ false);
    }
}

测试 builder 的作用不是模拟完整 PMS,而是把一个变量注入 ScanRequest,然后直接调用 scanPackageOnly()。它默认 enableAlignmentChecks=false,所以测试不会证明 SettingsProvider alignment 条件;需要证明该条件时,必须看实际安装路径的 prepareInitialScanRequest(),不能从 builder 测试外推。

6.2 首次与更新 ​

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

java
@Test
public void newInstallSimpleAllNominal() throws Exception {
    final ScanRequest scanRequest =
            createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
                    .addScanFlag(PackageManagerService.SCAN_NEW_INSTALL)
                    .addScanFlag(PackageManagerService.SCAN_AS_FULL_APP)
                    .build();

    final ScanResult scanResult = executeScan(scanRequest);

    assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
    assertThat(scanResult.mExistingSettingCopied, is(false));
    assertPathsNotDerived(scanResult);
}

@Test
public void updateSimpleNominal() throws Exception {
    when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
    final PackageSetting pkgSetting = createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
            .setPrimaryCpuAbiString("primaryCpuAbi")
            .setSecondaryCpuAbiString("secondaryCpuAbi")
            .build();
    final ScanRequest scanRequest =
            createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
                    .addScanFlag(PackageManagerService.SCAN_AS_FULL_APP)
                    .setPkgSetting(pkgSetting)
                    .build();

    final ScanResult scanResult = executeScan(scanRequest);

    assertThat(scanResult.mExistingSettingCopied, is(true));
    assertNotSame(pkgSetting, scanResult.mPkgSetting);
    assertThat(scanResult.mPkgSetting.getPrimaryCpuAbiLegacy(), is("primaryCpuAbi"));
    assertThat(scanResult.mPkgSetting.getSecondaryCpuAbiLegacy(), is("secondaryCpuAbi"));
}

这两个测试的 arrange/action/assert 范围不同:

  • newInstallSimpleAllNominal 没有旧 PackageSetting,断言结果没有复制旧设置,证明首次扫描走创建分支。
  • updateSimpleNominal 注入已有 ABI,断言结果是不同对象且 ABI 被保留,证明更新扫描复制设置而不是修改测试持有的原对象。

它们没有证明签名 reconcile、AppId 注册或 commit 成功;测试只覆盖 scanPackageOnly() 的结果边界。

6.3 库与特殊状态 ​

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

java
@Test
public void installDynamicLibraries() throws Exception {
    final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("dynamic.lib.pkg")
            .addLibraryName("liba")
            .addLibraryName("libb")
            .hideAsParsed())
            .setVersionCodeMajor(1)
            .setVersionCode(234)
            .setBaseApkPath("/some/path.apk")
            .setSplitCodePaths(new String[] {"/some/other/path.apk"});

    final ScanResult scanResult = executeScan(
            new ScanRequestBuilder(pkg).setUser(UserHandle.of(0)).build());

    final SharedLibraryInfo dynamicLib0 = scanResult.mDynamicSharedLibraryInfos.get(0);
    assertThat(dynamicLib0.getPackageName(), is("dynamic.lib.pkg"));
    assertThat(dynamicLib0.getName(), is("liba"));
    assertThat(dynamicLib0.getType(), is(TYPE_DYNAMIC));
    assertThat(dynamicLib0.getAllCodePaths(),
            hasItems("/some/path.apk", "/some/other/path.apk"));
    final SharedLibraryInfo dynamicLib1 = scanResult.mDynamicSharedLibraryInfos.get(1);
    assertThat(dynamicLib1.getName(), is("libb"));
}

@Test
public void scanFirstBoot_apexDontDeriveAbis() throws Exception {
    final PackageSetting pkgSetting = createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
            .setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build();
    final String codePath = "/data/apex/" + DUMMY_PACKAGE_NAME + ".apex";
    final ParsedPackage basicPackage =
            ((ParsedPackage) new PackageImpl(DUMMY_PACKAGE_NAME, codePath, codePath,
                    mock(TypedArray.class), false, null)
                    .setVolumeUuid(UUID_ONE.toString())
                    .hideAsParsed())
                    .setVersionCodeMajor(1)
                    .setVersionCode(2345)
                    .setSystem(true);

    final ScanResult scanResult = executeScan(new ScanRequestBuilder(basicPackage)
            .setPkgSetting(pkgSetting)
            .addScanFlag(SCAN_FIRST_BOOT_OR_UPGRADE | SCAN_AS_APEX)
            .build());

    final PackageSetting resultSetting = scanResult.mPkgSetting;
    final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
            resultSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, resultSetting);
    assertThat(applicationInfo.primaryCpuAbi, nullValue());
    assertThat(applicationInfo.nativeLibraryRootDir, nullValue());
}

installDynamicLibraries 的关键断言是库名、类型、声明包和 base/split code paths,证明 ScanResult 确实把解析出的 dynamic library 描述带出来;它没有证明库已注册到系统共享库表。APEX 测试则断言 SCAN_AS_APEX 下不从普通 APK 路径推导 native ABI,证明 scan flag 会改变 scanPackageOnly() 的 ABI 分支。

6.4 重命名与用户状态 ​

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

java
@Test
public void installRealPackageName() throws Exception {
    final ScanResult scanResult = executeScan(
            createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
                    .setRealPkgName("com.package.real")
                    .build());

    assertThat(scanResult.mPkgSetting.getRealName(), is("com.package.real"));

    final ScanResult scanResultNoReal = executeScan(
            createBasicScanRequestBuilder(
                    createBasicPackage(DUMMY_PACKAGE_NAME)
                            .addOriginalPackage("com.package.real"))
                    .build());
    assertThat(scanResultNoReal.mPkgSetting.getRealName(), nullValue());
}

@Test
public void updateInstant_changeToFull() throws Exception {
    when(mMockUserManager.getUserIds()).thenReturn(new int[]{0});
    final PackageSetting existingPkgSetting =
            createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
                    .setInstantAppUserState(0, true)
                    .build();
    final ScanResult scanResult = executeScan(
            createBasicScanRequestBuilder(createBasicPackage(DUMMY_PACKAGE_NAME))
                    .setPkgSetting(existingPkgSetting)
                    .addScanFlag(SCAN_AS_FULL_APP)
                    .build());

    assertBasicPackageScanResult(scanResult, DUMMY_PACKAGE_NAME, false /*isInstant*/);
}

第一个测试证明 mRealPkgName 会进入新建 PackageSetting 的 real name;第二个测试证明现有用户状态可以被 scan flags 调整,从 instant app 切回 full app。它们覆盖的是设置派生逻辑,不等于证明重命名后的组件已经完成 commit 或广播。

6.5 缺失结果 ​

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

java
public void assertScanResultExists() {
    if (mScanResult == null) {
        // Should not happen. This indicates a bug in the installation code flow
        if (Build.IS_USERDEBUG || Build.IS_ENG) {
            throw new IllegalStateException("ScanResult cannot be null.");
        } else {
            Slog.e(TAG, "ScanResult is null and it should not happen");
        }
    }
}

InstallRequest 在 userdebug/eng 构建中把“在 setScanResult 前读取结果”升级为异常;user 构建只记录错误日志。这个条件路径很重要:线上设备不一定因为缺失结果立即崩溃,但后续 getter 仍可能返回空引用并造成更晚的错误。调试时应优先检查 setScanResult() 是否执行,以及扫描异常是否被 scanInstallPackages() 捕获后写入 request error。

7. 阅读与诊断 ​

7.1 源码搜索 ​

遇到一个扫描状态异常,可以按下面的真实符号顺序搜索:

  1. InstallPackageHelper.prepareInitialScanRequest():确认历史设置、shared user、重命名和用户来源。
  2. InstallPackageHelper.scanPackageNew():比较 newScanFlags 与传入 scanFlags,确认 policy 调整是否已发生。
  3. ScanPackageUtils.scanPackageOnly():确认创建/复制设置、ABI、时间、seInfo 和共享库结果。
  4. ScanResult 构造函数:确认 mExistingSettingCopied 和 mPreviousAppId 的真实值。
  5. InstallRequest.setScanResult() 与 getters:确认结果是否被消费。
  6. ReconcilePackageUtils.reconcilePackages():确认签名、替换删除、共享库冲突和 shared user 分支。
  7. commitReconciledScanResultLocked():确认结果何时合并回正式设置并进入 live structures。

可以在源码树中使用:

bash
rg -n "prepareInitialScanRequest|scanPackageNew|scanPackageOnly" \
    frameworks/base/services/core/java/com/android/server/pm
rg -n "setScanResult|getScannedPackageSetting|isExistingSettingCopied" \
    frameworks/base/services/core/java/com/android/server/pm
rg -n "newInstallSimpleAllNominal|updateSimpleNominal|installDynamicLibraries" \
    frameworks/base/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm

第一组搜索回答“请求怎样构造并扫描”,第二组回答“结果由谁读取”,第三组把源码结论与测试断言对上。不要只搜索类名,因为同名变量在安装、APEX 和测试路径中承担的 owner 不同。

7.2 状态对照 ​

现象首先检查能够证明什么不能直接证明什么
结果设置与旧对象不同mExistingSettingCopied、assertNotSame更新扫描使用副本尚未 commit
realName 错误mRealPkgName、mOriginalPkgSetting重命名输入是否进入新设置组件解析是否完成
ABI 丢失SCAN_FIRST_BOOT_OR_UPGRADE、SCAN_AS_APEX、pkgSetting ABIderive/reuse 分支选择native 库文件是否实际可加载
shared library 冲突mDynamicSharedLibraryInfos、incomingSharedLibraries本批次声明是否重复所有外部依赖是否可解析
AppId 泄漏optimisticallyRegisterAppIds()、cleanUpAppIdCreations()乐观分配是否被清理commit 中途异常的全部回滚
查询不到包setScanResult()、reconcile、commit结果是否流过各阶段parse 成功就一定可查询

7.3 只读验证 ​

设备上可以用以下命令把源码状态和运行时状态对照起来:

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

对照时至少记录:

  • code path、版本号、realName 和是否 updated system app;
  • AppId、shared user、instant/full app 用户状态;
  • primary/secondary ABI、native library path 和 seInfo;
  • shared library 的声明包、版本和 code paths;
  • 扫描失败、重复包、签名不兼容、替换删除失败和 AppId 清理日志。

这些命令只能观察已经提交或被日志打印的状态,不能直接显示局部 ScanRequest。如果 dumpsys 与预期不符,应回到 prepareInitialScanRequest() 和 scanPackageOnly(),检查是输入历史设置错误、policy 调整错误,还是结果没有通过 reconcile/commit。

8. 数据边界 ​

ScanRequest 与 ScanResult 的价值不在于字段多,而在于它们把 PMS 扫描流程切成了可追踪的输入和输出边界:

  • ScanRequest 保存解析包、旧运行时包、当前/disabled/original PackageSetting、shared user、flags 和用户上下文;其中 mPkgSetting 允许被修改,mOldPkgSetting 是扫描前副本。
  • prepareInitialScanRequest() 在 mLock 内读取 Settings,在锁外计算 alignment 条件,再由 scanPackageNew() 重新构造真正的扫描请求。
  • scanPackageOnly() 根据是否已有设置创建或复制 PackageSetting,同时更新 ABI、native library path、时间、SELinux seInfo、用户状态和共享库描述。
  • ScanResult 只代表扫描阶段输出;mExistingSettingCopied 控制后续设置合并,mPreviousAppId 在 Android 17 中固定为 INVALID_UID,不能声称当前存在 AppId 迁移结果。
  • InstallRequest 保存结果并向 reconcile 暴露 getter;reconcile 负责签名、替换删除、共享库冲突和 shared user 一致性;commit 才把结果写入 PMS 的正式状态。
  • 测试分别证明首次创建、更新复制、共享库生成、APEX ABI 分支、重命名和 instant/full 用户状态;它们不能替代完整安装、reconcile 和 commit 验证。

后续扫描专题会继续拆解并行解析和结果消费的性能边界,重点说明 ParallelPackageParser 如何把多个 ParsedPackage 送入这条请求/结果管线,以及为什么解析并行不等于状态注册并行。