PMS 全模块总结与知识图谱
这篇文章不是把前面的文章标题重新排列,而是给出一张可以实际走源码的地图。读者需要先了解 Binder、SystemServer 和基本的 APK 安装概念;前置可读 PMS 启动时序、ComputerEngine 查询引擎、安装全景 和 dexopt 流程总览。
本文不再解释每个专题的全部字段。它回答的是四个跨模块问题:一个包从哪里进入 PMS,状态由谁拥有,状态变化由谁消费,以及失败时应该沿哪条反向路径定位。所有源码路径均对应 Android 17 android-17.0.0_r1。
1. 先找入口
读 PMS 时先按事件选择入口,不要从 PackageManagerService 的成员变量列表开始。真实入口可以分成四类:
| 事件 | 第一处源码 | 首要消费者 |
|---|---|---|
| system_server 启动 | PackageManagerService 构造函数、InitAppsHelper.initSystemApps | Settings、扫描器、ApexManager |
| 安装/更新 | PackageInstallerService、PackageInstallerSession | InstallingSession、InstallPackageHelper |
| 查询/状态修改 | PackageManagerService.IPackageManagerImpl、PackageManagerInternal | Computer、PackageStateMutator |
| 运行后反馈 | notifyDexLoad、UsageStats、CrashRecovery observer | DexUseManagerLocal、AppHibernation、PackageWatchdog |
入口选择决定你应该先追“事务”还是“快照”。安装和卸载是写路径;查询、dex load 和健康监控通常先取得 snapshot,再把结果交给专门 owner。
2. 状态的唯一 owner
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java、frameworks/base/services/core/java/com/android/server/pm/Settings.java、frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java。
PMS 的长期包状态最终落在 Settings 管理的 PackageSetting/PackageState 体系;查询不会直接遍历可变对象,而是通过 Computer 读取一个一致视图。写路径使用 commitPackageStateMutation:
public PackageStateMutator.Result commitPackageStateMutation(
PackageStateMutator.InitialState initialState,
String packageName, Consumer<PackageStateWrite> consumer) {
synchronized (mLock) {
PackageStateMutator mutator = new PackageStateMutator(initialState);
consumer.accept(mutator.forPackage(packageName));
PackageStateMutator.Result result = mutator.commit(
mSettings.getPackagesLocked());
if (result.isCommitted()) {
scheduleWritePackageRestrictions(/* userId */ UserHandle.USER_ALL);
}
return result;
}
}阅读时要区分三层对象:
AndroidPackage:解析后的不可变 APK 描述,偏代码和 manifest。PackageState/PackageSetting:安装身份、版本、用户状态、路径和运行时包状态。Computer:对外查询使用的快照视图,避免调用方在锁外读到半次 mutation。
packages.xml 等持久化文件不是状态 owner;它们只是 Settings 的落盘形式。看到 XML 改变时,应反向搜索哪个 mutation 产生了字段变化,再追写盘调度。
3. 启动汇合点
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java、frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java。
启动阶段先恢复 Settings 和系统配置,之后 InitAppsHelper 扫描 APEX 与 APK。Android 17 的 APEX 路径不是普通 data-app 扫描的旁路:
List<ApexManager.ScanResult> apexScanResults =
scanApexPackagesTraced(packageParser);
mApexManager.notifyScanResult(apexScanResults);
for (ApexManager.ActiveApexInfo apexInfo
: mApexManager.getActiveApexInfos()) {
String apexPackageName = apexInfo.moduleName;
for (String packageName : mApexManager.getApksInApex(apexPackageName)) {
// 将 APK-in-APEX 纳入包状态和可见性计算
}
}之后普通分区的扫描结果进入 InstallPackageHelper/ScanPackageUtils,并与已恢复的 package state 合并。启动的关键屏障不是“所有文件都读完”,而是包代码路径、用户状态和 APEX 激活结果都能供后续 Computer 查询。
如果问题发生在早期启动,先看 InitAppsHelper 的扫描结果;如果包已经可查询但用户状态错误,再转向 PackageSetting 和 PackageUserState;如果只有 APK-in-APEX 不可见,继续看 ApexManager.notifyScanResult 后的关联逻辑。
4. 安装的写路径
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java、frameworks/base/services/core/java/com/android/server/pm/InstallingSession.java、frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java。
安装不是一个函数调用,而是会话、校验、文件移动、包状态提交和后续优化的链:
其中 PackageInstallerSession 拥有会话文件和提交状态;InstallPackageHelper 拥有 PMS 侧的包安装事务;PackageState 拥有提交后的身份。不要把 session 的 isCommitted() 当作包已经可查询,必须继续追到 mutation 和包扫描结果。
安装后 dexopt 是后续消费者,不是状态提交的一部分。InstallPackageHelper 在适当条件下调用 performDexoptIfNeededAsync,这解释了安装成功但 oat/vdex 尚未生成的现象。
5. 查询与快照
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java、frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java。
Binder 查询通常取得 Computer/snapshotComputer(),再由查询对象完成可见性、用户和组件过滤。写路径则在 mLock 内提交 mutation,再让后续 snapshot 看到新 revision。
这套设计带来一个调试规则:
- 先确认调用使用的是哪个 snapshot,以及它创建于 mutation 之前还是之后;
- 再检查
shouldFilterApplication、canQueryPackage和 userId; - 最后才判断包状态本身是否错误。
很多“安装成功但查询不到”其实是 visibility 过滤,不是安装事务丢失。反向证据应使用同一 user、同一 calling UID 的查询,而不是只看 system UID 的 dump。
6. ART 与运行时反馈
源码文件:frameworks/base/services/core/java/com/android/server/pm/DexOptHelper.java、art/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java。
PMS 的 dexopt owner 是 DexOptHelper/ART Service;运行时 dex 使用由 DexUseManagerLocal 记录。两条链的连接点是包状态和 dex 路径,而不是旧式的单一 DCL 类。
DexUseManagerLocal dexUseManager = DexOptHelper.getDexUseManagerLocal();
try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
LocalManagerRegistry.getManager(PackageManagerLocal.class)
.withUnownedFilteredSnapshot(snapshot)) {
dexUseManager.notifyDexContainersLoaded(
filteredSnapshot, loadingPackageName,
classLoaderContextByDexContainerFile);
}DexUseManagerLocal 通过 owner 查找把路径归到 primary 或 secondary dex,保存 loader、user、ABI 和 class-loader context,再由后台 dexopt 消费。其 protobuf 持久化和后台调度与 DynamicCodeLogger 的 SafetyNet DCL 文本记录相互独立。
阅读 ART 问题时应使用两条证据:getCheckedSecondaryDexInfo/dexopt 日志证明 ART 是否认识该 dex;DynamicCodeLogger 事件证明安全日志是否记录过动态加载。一个有记录不代表另一个也有记录。
7. 用户与休眠
源码文件:frameworks/base/services/core/java/com/android/server/apphibernation/AppHibernationService.java、frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java。
用户级状态由 AppHibernationService 按 user 保存,进入休眠后 force-stop 并清理缓存;全局状态可通过 PackageManagerInternal.deleteOatArtifactsOfPackage 让 ART 删除编译产物。PMS 通过 LocalServices 联动 unstop:
AppHibernationManagerInternal ah =
mInjector.getLocalService(AppHibernationManagerInternal.class);
if (ah != null && ah.isHibernatingForUser(packageName, userId)) {
ah.setHibernatingForUser(packageName, userId, false);
ah.setHibernatingGlobally(packageName, false);
}这段代码说明休眠状态不是 PackageUserState 的简单布尔字段。它由独立服务拥有,但 PMS 的 stopped 状态变化会触发解除。定位休眠问题时要同时看 user CE 的 hibernation proto、PMS stopped state 和 ActivityManager force-stop 记录。
8. APEX 与包身份
源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java、frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java。
ApexManager 是 PMS 与 apexd 的边界,负责 active APEX、session、APK-in-APEX 和 module name 查询。PMS 不直接解析 APEX 激活事务的底层状态,而是在扫描阶段取得 ActiveApexInfo/ScanResult,将其中的包纳入自己的包模型。
这解释了两个常见误区:
- APEX session 已 ready 不等于 APK-in-APEX 已经在 PMS 查询表中;还要经过扫描结果提交。
- APEX 回滚由 Rollback/ apexd 协作完成,不等于普通 APK
PackageInstallerSession的文件替换。
跨 APEX/APK 问题应沿“session → apexd active state → ApexManager 查询 → PMS scan result → PackageState”顺序取证。
9. 恢复与故障链
源码文件:platform/packages/modules/CrashRecovery/service/java/com/android/server/PackageWatchdog.java、platform/packages/modules/CrashRecovery/service/java/com/android/server/rollback/RollbackPackageHealthObserver.java。
PackageWatchdog 只负责失败窗口、observer 选择和调用时机。Rollback observer 再查询可用 rollback,按用户影响等级选择包或 APEX 回滚;RescueParty 可能返回更低 impact,因此最终动作不是固定的 rollback。
MITIGATION_RESULT_SUCCESS 只表示 observer 接受并排队动作。要验证恢复完成,还需继续看 RollbackManager session、staged reboot、PMS 再扫描和 PackageWatchdog 后续失败计数。
10. 状态持久化地图
跨模块阅读时,文件名本身比“数据库”这个泛称更有用:
| 状态 | 主要 owner | 持久化/证据 | 恢复方式 |
|---|---|---|---|
| 包身份与安装状态 | Settings / PackageState | packages settings 与 restrictions | 启动扫描、mutation reconcile |
| dex 使用 | DexUseManagerLocal | /data/system/package-dex-usage.pb | ART Service cleanup |
| DCL 安全记录 | PackageDynamicCodeLoading | package-dcl.list | 包/user sync、文件 hash |
| 休眠状态 | AppHibernationService | system/system_ce 下 hibernation proto | package/stopped reconcile |
| watchdog observer | PackageWatchdog | AtomicFile XML、boot metadata | observer 重新注册、BootThreshold |
| APEX 激活 | ApexManager/apexd | staged session 与 apexd 状态 | 下次启动激活/回滚后重扫 |
持久化文件是恢复输入,不是最终事实。每个 owner 都有一次启动验证或 cleanup:包状态要和扫描结果一致,dex 记录要和文件可见性一致,休眠要和 stopped 状态一致,watchdog 要和重新注册的 observer 一致。
11. 一条完整阅读路线
面对一个“安装后不可用”的问题,可以按以下源码顺序走:
- 从
PackageInstallerSession.commit确认 session 是否进入提交路径; - 进入
InstallingSession和InstallPackageHelper,确认解析、签名、复制和commitPackageStateMutation; - 用同一 user/UID 创建
Computer查询,确认包可见性和PackageUserState; - 若启动失败,查看
DexOptHelper、ART artifact 状态和DexUseManagerLocal的 CLC/ABI; - 若是 APEX/APK-in-APEX,回到
ApexManageractive/session 与InitAppsHelperscan result; - 若反复崩溃,查看
PackageWatchdogfailure reason、impact 和 Rollback/RescueParty 的执行 session。
这条路线的关键是每一步都问“谁拥有状态,谁消费变化”。不要用 pm list packages 的一次输出替代安装事务、用户过滤和 ART 编译三种不同证据。
12. 结尾练习
- 选择一个已安装包,分别画出“包状态查询”和“dex 使用记录”的 owner/consumer;指出为什么两者都能提到同一个 APK 路径,却不共享同一持久化文件。
- 给定“APEX session 已提交但包不可见”的现象,按
ApexManager、InitAppsHelper、PackageState和Computer的顺序列出四个检查点。 - 给定“应用五次崩溃但没有回滚”,区分 failure window 未达到、impact threshold 阻止、RescueParty 被选中和 rollback session 异步失败四种可能。
- 修改某个用户的 stopped/hibernation 状态后,预测哪些服务会收到联动:PMS、ActivityManager、AppHibernationService、ART Service 中哪些会变化,哪些不会。
