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.javaframeworks/base/services/core/java/com/android/server/pm/ScanResult.java
ParsedPackage、PackageSetting 和 AndroidPackage 在这条链上不是同一层对象:
| 对象 | 所有者 | 进入时机 | 主要消费者 |
|---|---|---|---|
ParsedPackage | Package parser / 当前扫描流程 | APK 或 APEX 内 APK 解析结束 | applyPolicy()、scanPackageOnly()、reconcile |
PackageSetting | Settings | 从持久化设置读取,或扫描时新建/复制 | AppId、用户状态、ABI、路径、commit |
AndroidPackage | PackageSetting.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
/** 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;字段可以按责任分为四组:
mParsedPackage是本次输入;mOldPkg是被替换包的运行时视图。mPkgSetting、mDisabledPkgSetting、mOriginalPkgSetting和mOldPkgSetting描述当前、disabled、原始包名和扫描前副本。mOldSharedUserSetting与mSharedUserSetting用来比较 shared UID 是否改变,并把当前包接入正确的共享用户。- 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
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
/**
* 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
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
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);
}
}这里不是“同一个请求被重复初始化”,而是有两个不同用途的请求:
initialScanRequest在读取Settings和计算 alignment 开关时作为上下文容器。- 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
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
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
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
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
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
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
/** 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
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.javaframeworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.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
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
@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
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
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
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
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
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
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
@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
@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
@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
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 源码搜索
遇到一个扫描状态异常,可以按下面的真实符号顺序搜索:
InstallPackageHelper.prepareInitialScanRequest():确认历史设置、shared user、重命名和用户来源。InstallPackageHelper.scanPackageNew():比较newScanFlags与传入scanFlags,确认 policy 调整是否已发生。ScanPackageUtils.scanPackageOnly():确认创建/复制设置、ABI、时间、seInfo和共享库结果。ScanResult构造函数:确认mExistingSettingCopied和mPreviousAppId的真实值。InstallRequest.setScanResult()与 getters:确认结果是否被消费。ReconcilePackageUtils.reconcilePackages():确认签名、替换删除、共享库冲突和 shared user 分支。commitReconciledScanResultLocked():确认结果何时合并回正式设置并进入 live structures。
可以在源码树中使用:
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 ABI | derive/reuse 分支选择 | native 库文件是否实际可加载 |
| shared library 冲突 | mDynamicSharedLibraryInfos、incomingSharedLibraries | 本批次声明是否重复 | 所有外部依赖是否可解析 |
| AppId 泄漏 | optimisticallyRegisterAppIds()、cleanUpAppIdCreations() | 乐观分配是否被清理 | commit 中途异常的全部回滚 |
| 查询不到包 | setScanResult()、reconcile、commit | 结果是否流过各阶段 | parse 成功就一定可查询 |
7.3 只读验证
设备上可以用以下命令把源码状态和运行时状态对照起来:
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/originalPackageSetting、shared user、flags 和用户上下文;其中mPkgSetting允许被修改,mOldPkgSetting是扫描前副本。prepareInitialScanRequest()在mLock内读取 Settings,在锁外计算 alignment 条件,再由scanPackageNew()重新构造真正的扫描请求。scanPackageOnly()根据是否已有设置创建或复制PackageSetting,同时更新 ABI、native library path、时间、SELinuxseInfo、用户状态和共享库描述。ScanResult只代表扫描阶段输出;mExistingSettingCopied控制后续设置合并,mPreviousAppId在 Android 17 中固定为INVALID_UID,不能声称当前存在 AppId 迁移结果。InstallRequest保存结果并向 reconcile 暴露 getter;reconcile 负责签名、替换删除、共享库冲突和 shared user 一致性;commit 才把结果写入 PMS 的正式状态。- 测试分别证明首次创建、更新复制、共享库生成、APEX ABI 分支、重命名和 instant/full 用户状态;它们不能替代完整安装、reconcile 和 commit 验证。
后续扫描专题会继续拆解并行解析和结果消费的性能边界,重点说明 ParallelPackageParser 如何把多个 ParsedPackage 送入这条请求/结果管线,以及为什么解析并行不等于状态注册并行。
