Skip to content

安装后注册

追踪 Android 17 安装提交后 Settings、组件、KeySet、权限、可见性和域名状态的注册顺序。

基于android-17.0.0_r1
AndroidPackageManagerServiceInstallPackageHelperComponentResolverPermissionManager源码阅读

安装后注册 ​

本文承接 安装时 dexopt、InstallPackageHelper 入口、IntentFilter 解析 和 共享库依赖解析。前文已经讲了包如何被解析、扫描、协调和编译;本文聚焦 commit 阶段之后,PMS 如何让一个包真正“可查询、可解析、可授予权限、可被持久化”。

这里的“注册”不是一个 API 调用,而是 InstallPackageHelper.commitPackagesLocked() 对每个 ReconciledPackage 执行的一组有顺序的状态写入。Settings、mPackages、ComponentResolver、KeySetManagerService、AppsFilter、DomainVerificationManager 和 PermissionManagerService 各自拥有一部分状态;任何一个阶段漏掉,都会出现“APK 已在磁盘上但系统查不到组件”或“包表存在但权限/域名状态不完整”的问题。

1. 注册总览 ​

1.1 Commit 主线 ​

1.2 生效状态 ​

状态真实 ownercommit 中的动作
package identity/UIDPackageSetting + mPackages写入或替换包设置
组件和 IntentFilterComponentResolveraddAllComponents()
KeySetKeySetManagerServiceaddScannedPackageLPw()
包可见性AppsFilteraddPackage()
域名验证DomainVerificationManageradd 或 migrate state
权限声明/状态PermissionManagerServiceonPackageAdded()
package propertiesPMS property registryaddAllPackageProperties()

2. commit 外壳 ​

2.1 遍历批次 ​

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

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 替换旧包 ​

java
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

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 ​

java
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 签名与共享库状态 ​

java
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

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 注册 ​

java
if ((scanFlags & SCAN_AS_APEX) == 0) {
    KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
    ksms.addScannedPackageLPw(pkg);
}

APEX 不把 KeySet 持久化到普通 package settings,因此跳过这一步;普通 APK 则把扫描得到的 KeySet 加入全局服务,供后续升级 keyset 检查。

4.3 组件与可见性 ​

java
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 状态 ​

java
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

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 ​

java
final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts();
if (!protectedBroadcasts.isEmpty()) {
    synchronized (mPm.mProtectedBroadcasts) {
        mPm.mProtectedBroadcasts.addAll(protectedBroadcasts);
    }
}

包声明的 protected broadcast 使用独立集合和独立锁,不随组件 resolver 注册。它的生效对象是 PMS 广播权限检查,而不是 IntentFilter 查询。

5.4 Permissions ​

java
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

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 用户状态 ​

java
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

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 组件移除对称性 ​

java
// 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 清理 ​

java
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 含 IntentFilterComponentResolver 查询到组件component index
普通 APKKeySet 注册成功upgrade key
APEX跳过普通 KeySet 持久化APEX 分支
新包 app linksDomainVerification addPackage 调用域名状态
更新包 app linksmigrateState 保留旧状态状态迁移
Instant AppPermissionManager 收到 instant 标志权限分支
protected broadcast集合中出现 action广播安全
用户不存在request 写入 internal errorupdateSettings
commit 完成UID/AppOps cache 被失效缓存一致性

测试不能只检查 APK 文件是否在 /data/app;至少要同时验证 package table、resolver、权限、域名和用户状态,否则无法证明“安装后注册”真正完成。

9.2 现场排查顺序 ​

  1. 查看 commitPackagesLocked() 是否处理了目标 request,确认是否在 replace 前失败。
  2. 检查 commitReconciledScanResultLocked() 是否成功冻结 ParsedPackage、UID、签名和共享库状态。
  3. 检查 Settings.insertPackageSettingLPw() 与 mPackages.put() 是否都执行。
  4. 分别检查 KeySet、ComponentResolver、AppsFilter、DomainVerification 和 PermissionManager 的注册日志。
  5. 对更新包检查旧 resolver filter 和旧 Settings 是否已移除。
  6. 最后检查 updateSettingsLI() 的 per-user 状态及 UID/AppOps cache invalidation。

10. 源码路线 ​

建议按以下顺序阅读:

  1. InstallPackageHelper.commitPackagesLocked():批次循环、替换旧包和最终收尾。
  2. commitReconciledScanResultLocked():PackageSetting、SharedUser、UID、签名和共享库。
  3. commitPackageSettings():Settings、mPackages、KeySet、resolver、AppsFilter 和权限入口。
  4. updateSettingsLI():用户状态、system package enable/installed 和 overlay。
  5. ComponentResolver.addAllComponents():组件与 IntentFilter 索引。
  6. PermissionManagerService.onPackageAdded():权限注册和 Instant 分支。
  7. DomainVerificationManager.addPackage/migrateState():新包与更新包域名状态。
  8. completeInstallProcess()、doPostInstall():cache invalidation、旧路径清理和 observer。

11. 设计收束 ​

安装后注册的真实链路是:

text
ReconciledPackage
  -> 处理旧包/共享用户/签名/共享库
  -> 固化 PackageSetting 与 AndroidPackage
  -> 写入 Settings 和 PMS.mPackages
  -> 注册 KeySet、组件、可见性、域名、Instrumentation、广播、权限
  -> 更新 per-user 状态并失效缓存

“安装成功”只有在这些 owner 都得到一致状态后才有完整意义。APK 文件、ParsedPackage、PackageSetting、resolver 索引和 PermissionManager 各自保存不同部分;commit 的顺序保证它们在同一个 PMS 锁保护下逐步汇合。理解这条注册链,才能解释为什么文件已经移动但 Intent 仍解析不到、为什么更新后旧组件残留、以及为什么新的 UID/权限状态要等 cache invalidation 后才对调用方可见。