安装后注册
本文承接 安装时 dexopt、InstallPackageHelper 入口、IntentFilter 解析 和 共享库依赖解析。前文已经讲了包如何被解析、扫描、协调和编译;本文聚焦 commit 阶段之后,PMS 如何让一个包真正“可查询、可解析、可授予权限、可被持久化”。
这里的“注册”不是一个 API 调用,而是 InstallPackageHelper.commitPackagesLocked() 对每个 ReconciledPackage 执行的一组有顺序的状态写入。Settings、mPackages、ComponentResolver、KeySetManagerService、AppsFilter、DomainVerificationManager 和 PermissionManagerService 各自拥有一部分状态;任何一个阶段漏掉,都会出现“APK 已在磁盘上但系统查不到组件”或“包表存在但权限/域名状态不完整”的问题。
1. 注册总览
1.1 Commit 主线
1.2 生效状态
| 状态 | 真实 owner | commit 中的动作 |
|---|---|---|
| package identity/UID | PackageSetting + mPackages | 写入或替换包设置 |
| 组件和 IntentFilter | ComponentResolver | addAllComponents() |
| KeySet | KeySetManagerService | addScannedPackageLPw() |
| 包可见性 | AppsFilter | addPackage() |
| 域名验证 | DomainVerificationManager | add 或 migrate state |
| 权限声明/状态 | PermissionManagerService | onPackageAdded() |
| package properties | PMS property registry | addAllPackageProperties() |
2. commit 外壳
2.1 遍历批次
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
@GuardedBy("mPm.mLock")
private void commitPackagesLocked(List<ReconciledPackage> reconciledPackages,
@NonNull int[] allUsers) {
for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
final ParsedPackage parsedPackage = installRequest.getParsedPackage();
final String packageName = parsedPackage.getPackageName();
installRequest.onCommitStarted();
if (installRequest.isInstallReplace()) {
AndroidPackage oldPackage = mPm.mPackages.get(packageName);
// Remove old package or disable old system package before adding new state.
// Details are handled by delete/remove helpers.
}
AndroidPackage pkg = commitReconciledScanResultLocked(reconciledPkg, allUsers);
updateSettingsLI(pkg, allUsers, installRequest);
installRequest.onCommitFinished();
}
PackageManagerService.invalidateGetPackagesForUidCache(
PackageMetrics.INVALIDATION_REASON_INSTALL_PACKAGE);
AppOpsManager.invalidateCheckPackageCache();
}commit 在 mPm.mLock 下逐包执行,替换安装先处理旧包,再提交新扫描结果和 Settings。批次中每个 request 都有自己的 commit metrics;整个循环结束后才失效 UID/AppOps 相关缓存。
2.2 替换旧包
if (installRequest.isInstallReplace()) {
AndroidPackage oldPackage = mPm.mPackages.get(packageName);
if (installRequest.isInstallSystem() && oldPackage != null) {
mRemovePackageHelper.removePackage(oldPackage, true);
if (!disableSystemPackageLPw(oldPackage)) {
installRequest.getRemovedInfo().mArgs =
new CleanUpArgs(packageName, oldPackage.getPath());
}
} else {
mDeletePackageHelper.executeDeletePackage(
reconciledPkg.mDeletePackageAction, packageName,
true, allUsers, false, installRequest.isKeepArtProfile());
}
}系统包更新与普通包更新的旧包处理不同:系统包可能被 disable,普通包使用 DeletePackageHelper 执行删除动作。删除旧包的物理清理信息写入 RemovedInfo,供 post-install 阶段继续处理。
3. 扫描结果固化
3.1 Setting 选择
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
final PackageSetting pkgSetting;
if (request.isExistingSettingCopied()) {
pkgSetting = request.getScanRequestPackageSetting();
pkgSetting.updateFrom(request.getScannedPackageSetting());
} else {
pkgSetting = request.getScannedPackageSetting();
if (request.getScanRequestOriginalPackageSetting() != null) {
mPm.mSettings.addRenamedPackageLPw(
AndroidPackageUtils.getRealPackageOrNull(parsedPackage, pkgSetting.isSystem()),
request.getScanRequestOriginalPackageSetting().getPackageName());
} else {
mPm.mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName());
}
}新包可能复用已有 PackageSetting,也可能使用扫描阶段新建的 setting。包名迁移关系在这里更新;这一步决定后续 mPackages 使用哪个逻辑名称。
3.2 SharedUser/UID
SharedUserSetting sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(pkgSetting);
if (sharedUserSetting != null) {
sharedUserSetting.addPackage(pkgSetting);
if (parsedPackage.isLeavingSharedUser()
&& SharedUidMigration.applyStrategy(BEST_EFFORT)
&& sharedUserSetting.isSingleUser()) {
mPm.mSettings.convertSharedUserSettingsLPw(sharedUserSetting);
}
}
parsedPackage.setUid(pkgSetting.getAppId());
final AndroidPackage pkg = parsedPackage.hideAsFinal();SharedUser 关系在 commit 时加入或迁移,随后把最终 appId 写入 ParsedPackage 并冻结为 AndroidPackage。从这个点开始,下游 resolver 和 PackageInfo 读取的是 final package,而不是可变解析对象。
3.3 签名与共享库状态
if (reconciledPkg.mCollectedSharedLibraryInfos != null
|| (oldPkgSetting != null
&& !oldPkgSetting.getSharedLibraryDependencies().isEmpty())) {
mSharedLibraries.executeSharedLibrariesUpdate(pkg, pkgSetting, null, null,
reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
}
if (reconciledPkg.mRemoveAppKeySetData) {
mPm.mSettings.getKeySetManagerService()
.removeAppKeySetDataLPw(pkg.getPackageName());
}
pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails);共享库依赖和签名状态在包设置进入全局表前先固化。KeySet 的移除/更新依据 Reconcile 结果,不能在注册组件后再补,否则中间查询可能看到不一致状态。
4. 注册设置
4.1 Package table
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
synchronized (mPm.mLock) {
// We don't expect installation to fail beyond this point.
mPm.mSettings.insertPackageSettingLPw(pkgSetting, pkg);
mPm.mPackages.put(pkg.getPackageName(), pkg);
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
mApexManager.registerApkInApex(pkg);
}
}Settings.insertPackageSettingLPw() 更新持久化模型,mPackages.put() 更新 PMS 当前包表。两者都在锁内完成;到这里 package identity 才同时存在于“可持久化设置”和“当前运行快照”。
4.2 KeySet 注册
if ((scanFlags & SCAN_AS_APEX) == 0) {
KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
ksms.addScannedPackageLPw(pkg);
}APEX 不把 KeySet 持久化到普通 package settings,因此跳过这一步;普通 APK 则把扫描得到的 KeySet 加入全局服务,供后续升级 keyset 检查。
4.3 组件与可见性
final Computer snapshot = mPm.snapshotComputer();
mPm.mComponentResolver.addAllComponents(
pkg, chatty, mPm.mSetupWizardPackage, snapshot);
mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace,
(scanFlags & SCAN_DONT_KILL_APP) != 0 /*retainImplicitGrantOnReplace*/);
mPm.addAllPackageProperties(pkg);组件 resolver 遍历 Activity、Receiver、Provider、Service 的 intents,把 ParsedIntentInfo 交给对应 IntentResolver;AppsFilter 更新包可见性关系;properties 则进入 PMS 的包属性索引。组件可查询的生效点是 addAllComponents(),不是 APK rename 本身。
5. 域名与权限
5.1 Domain 状态
if (!request.isArchived()) {
if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
mPm.mDomainVerificationManager.addPackage(
pkgSetting, request.getPreVerifiedDomains());
} else {
mPm.mDomainVerificationManager.migrateState(
oldPkgSetting, pkgSetting, request.getPreVerifiedDomains());
}
}新包创建域名验证状态,更新包迁移旧状态;归档安装跳过,因为归档对象没有声明的 app links。preVerifiedDomains 来自 session,Manifest filter 本身只提供候选 URI 数据。
5.2 instrumentation
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
for (ParsedInstrumentation instrumentation : pkg.getInstrumentations()) {
ComponentMutateUtils.setPackageName(instrumentation, pkg.getPackageName());
mPm.addInstrumentation(instrumentation.getComponentName(), instrumentation);
}Instrumentation 不进入 ComponentResolver 的四类 intent 索引,而是通过 PMS 单独的 component name 映射注册。包名在注册前补齐,保证查询得到完整 ComponentName。
5.3 Broadcast
final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts();
if (!protectedBroadcasts.isEmpty()) {
synchronized (mPm.mProtectedBroadcasts) {
mPm.mProtectedBroadcasts.addAll(protectedBroadcasts);
}
}包声明的 protected broadcast 使用独立集合和独立锁,不随组件 resolver 注册。它的生效对象是 PMS 广播权限检查,而不是 IntentFilter 查询。
5.4 Permissions
mPm.mPermissionManager.onPackageAdded(
pkgSetting, (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);权限管理在 package table、组件 resolver 等状态写入后收到 onPackageAdded()。参数包含 PackageSetting、是否 Instant App 和旧包,用于更新权限声明、授予状态和替换迁移。
6. 用户状态与持久化
6.1 updateSettingsLI
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
private void updateSettingsLI(AndroidPackage newPackage,
int[] allUsers, InstallRequest installRequest) {
updateSettingsInternalLI(newPackage, allUsers, installRequest);
}
private void updateSettingsInternalLI(AndroidPackage pkg,
int[] allUsers, InstallRequest installRequest) {
final String pkgName = pkg.getPackageName();
final int userId = installRequest.getUserId();
if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT
&& !mPm.mUserManager.exists(userId)) {
installRequest.setError(PackageManagerException.ofInternalError(
"User " + userId + " doesn't exist or has been removed",
PackageManagerException.INTERNAL_ERROR_MISSING_USER));
return;
}commit 固化 package 后,updateSettingsLI() 根据用户和系统包状态更新 installed/enabled、overlay paths、archive 和时间戳等 per-user 状态。用户不存在会在这里记录内部错误,说明 commit 之后仍可能有不可避免的状态检查。
6.2 System 用户状态
if (ps.isSystem()) {
if (!installRequest.isApplicationEnabledSettingPersistent()) {
for (int origUserId : installedForUsers) {
if (userId == UserHandle.USER_ALL || userId == origUserId) {
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
origUserId, installerPackageName);
}
}
}
for (int currentUserId : allUsers) {
ps.setInstalled(ArrayUtils.contains(installedForUsers, currentUserId),
currentUserId);
}
}系统包升级会根据原有用户安装集合恢复 enabled/installed 状态;普通用户安装则在对应 user 上设置状态。PackageSetting 的 per-user 状态不是由 mPackages.put() 自动产生,必须经过 updateSettings。
6.3 持久化时机
commitPackageSettings 写入 Settings 内存结构后,PMS 的 Settings 写线程/调用方负责把变更持久化到 packages.xml 和 user restrictions。解析对象不直接写 XML;注册阶段只更新 owner,持久化由 Settings 层完成。
7. 失败与清理
7.1 Commit 中途失败
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
/**
* Commits the package scan and modifies system state.
* WARNING: The method may throw an exception in the middle of committing the package,
* leaving the system in an inconsistent state.
*/
@GuardedBy("mPm.mLock")
private AndroidPackage commitReconciledScanResultLocked(...) {
// Commit package setting, shared user, signing and derived state.
}源码明确承认 commit 阶段仍可能因不可避免错误在中途抛出,因而不是完整事务。前置阶段应尽量把可预测错误排除,注册阶段依赖清理和下一次扫描恢复一致性。
7.2 组件移除对称性
// ComponentResolver.removeAllComponents() is used by package removal/update paths.
mPm.mComponentResolver.removeAllComponents(oldPackage, true /*chatty*/);更新或卸载旧包时,组件 resolver 必须移除旧 ParsedIntentInfo;Settings、KeySet、权限和域名状态也各有对应清理路径。只删除 APK 文件不会从 IntentResolver 中消除旧 filter。
7.3 Cache 清理
PackageManagerService.invalidateGetPackagesForUidCache(
PackageMetrics.INVALIDATION_REASON_INSTALL_PACKAGE);
AppOpsManager.invalidateCheckPackageCache();commit 批次结束后失效 UID 和 AppOps 检查缓存,使新 package table、UID 映射和 installer 身份立即可见。它与 ApplicationPackageManager 的查询缓存失效属于不同缓存 owner。
8. 时序与状态
8.1 注册时序
8.2 生效判断
| 现象 | 应检查的 owner |
|---|---|
getPackageInfo() 找不到包 | Settings、mPackages、commit 是否完成 |
queryIntentActivities() 找不到 | ComponentResolver.addAllComponents |
| 权限声明存在但状态不对 | PermissionManagerService.onPackageAdded、PackageSetting |
| App Links 状态丢失 | DomainVerificationManager.addPackage/migrateState |
| 更新后旧组件仍可解析 | resolver remove/add 对称性 |
| 新 UID/installer 检查仍返回旧值 | UID/AppOps cache invalidation |
9. 测试与诊断
9.1 输入与断言
| 场景 | 断言 | 证明范围 |
|---|---|---|
| 新包安装 | Settings 与 mPackages 都出现包 | package table |
| 更新包 | 旧包先移除,旧 code path 进入清理信息 | replace |
| Activity 含 IntentFilter | ComponentResolver 查询到组件 | component index |
| 普通 APK | KeySet 注册成功 | upgrade key |
| APEX | 跳过普通 KeySet 持久化 | APEX 分支 |
| 新包 app links | DomainVerification addPackage 调用 | 域名状态 |
| 更新包 app links | migrateState 保留旧状态 | 状态迁移 |
| Instant App | PermissionManager 收到 instant 标志 | 权限分支 |
| protected broadcast | 集合中出现 action | 广播安全 |
| 用户不存在 | request 写入 internal error | updateSettings |
| commit 完成 | UID/AppOps cache 被失效 | 缓存一致性 |
测试不能只检查 APK 文件是否在 /data/app;至少要同时验证 package table、resolver、权限、域名和用户状态,否则无法证明“安装后注册”真正完成。
9.2 现场排查顺序
- 查看
commitPackagesLocked()是否处理了目标 request,确认是否在 replace 前失败。 - 检查
commitReconciledScanResultLocked()是否成功冻结 ParsedPackage、UID、签名和共享库状态。 - 检查
Settings.insertPackageSettingLPw()与mPackages.put()是否都执行。 - 分别检查 KeySet、ComponentResolver、AppsFilter、DomainVerification 和 PermissionManager 的注册日志。
- 对更新包检查旧 resolver filter 和旧 Settings 是否已移除。
- 最后检查
updateSettingsLI()的 per-user 状态及 UID/AppOps cache invalidation。
10. 源码路线
建议按以下顺序阅读:
InstallPackageHelper.commitPackagesLocked():批次循环、替换旧包和最终收尾。commitReconciledScanResultLocked():PackageSetting、SharedUser、UID、签名和共享库。commitPackageSettings():Settings、mPackages、KeySet、resolver、AppsFilter 和权限入口。updateSettingsLI():用户状态、system package enable/installed 和 overlay。ComponentResolver.addAllComponents():组件与 IntentFilter 索引。PermissionManagerService.onPackageAdded():权限注册和 Instant 分支。DomainVerificationManager.addPackage/migrateState():新包与更新包域名状态。completeInstallProcess()、doPostInstall():cache invalidation、旧路径清理和 observer。
11. 设计收束
安装后注册的真实链路是:
ReconciledPackage
-> 处理旧包/共享用户/签名/共享库
-> 固化 PackageSetting 与 AndroidPackage
-> 写入 Settings 和 PMS.mPackages
-> 注册 KeySet、组件、可见性、域名、Instrumentation、广播、权限
-> 更新 per-user 状态并失效缓存“安装成功”只有在这些 owner 都得到一致状态后才有完整意义。APK 文件、ParsedPackage、PackageSetting、resolver 索引和 PermissionManager 各自保存不同部分;commit 的顺序保证它们在同一个 PMS 锁保护下逐步汇合。理解这条注册链,才能解释为什么文件已经移动但 Intent 仍解析不到、为什么更新后旧组件残留、以及为什么新的 UID/权限状态要等 cache invalidation 后才对调用方可见。
