PackageManagerLocal快照诊断
PackageManagerLocal 已经在 PackageManagerLocal 中介绍过接口。本篇不重复接口清单,而是回答一个调试问题:为什么同一个包在两个 system_server 消费者中得到不同结果,以及如何判断差异来自快照时间点、调用者过滤还是错误的关闭时机。
读者需要了解 Computer、AutoCloseable 和 PMS 的 user/package visibility;文中的结论均以 Android 17 frameworks/base 源码为准。本文只讨论只读 snapshot,不展开安装事务或 ART 编译算法。
1. 三个时间点
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerLocal.java
@NonNull
UnfilteredSnapshot withUnfilteredSnapshot();
@NonNull
FilteredSnapshot withFilteredSnapshot();
@NonNull
FilteredSnapshot withUnownedFilteredSnapshot(@NonNull PackageDataSnapshot computer);withUnfilteredSnapshot() 和 withFilteredSnapshot() 都取得并拥有一个底层 Computer;withUnownedFilteredSnapshot() 只借用调用方已有的快照。调试时必须先记录三个时间点:PMS 发布 Computer 的时间、消费者创建 snapshot 的时间、消费者实际读取字段的时间。snapshot 保证的是一个读取窗口内的一致视图,不保证跨两个 snapshot 的比较仍然一致。
2. 所有权检查
源码文件:frameworks/base/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
private abstract static class BaseSnapshotImpl implements AutoCloseable {
@Nullable private Computer mSnapshot;
private final boolean mUnowned;
BaseSnapshotImpl(Computer snapshot, boolean unowned) {
mSnapshot = snapshot;
mUnowned = unowned;
}
protected final Computer snapshot() {
if (mSnapshot == null) {
throw new IllegalStateException("Snapshot already closed");
}
return mSnapshot;
}
@Override
public final void close() {
if (!mUnowned) {
mSnapshot = null;
}
}
}真正的诊断信号不是 close() 返回值,而是关闭后再次访问会抛出 IllegalStateException。mUnowned 防止借用的 Computer 被子视图错误释放;拥有型 snapshot 则通过置空引用阻止 use-after-close。看到跨线程保存 snapshot 的代码,应优先检查是否把 AutoCloseable 当成长期缓存。
3. 过滤身份
源码文件:frameworks/base/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
public FilteredSnapshot withFilteredSnapshot(int callingUid, UserHandle user) {
final Computer computer = mService.snapshotComputer();
return new FilteredSnapshotImpl(computer, callingUid, user);
}
@Nullable
public PackageState getPackageState(@NonNull String packageName) {
return snapshot().getPackageStateFiltered(
packageName, mCallingUid, mUser.getIdentifier());
}同一个 packageName 在 unfiltered 和 filtered view 中不同并不表示 Settings 数据冲突。filtered 查询把 callingUid、user 和 visibility policy 一起交给 Computer;不可见包通常表现为 null,与包不存在难以从单次查询区分。调试时要同时打印 UID、userId 和 snapshot 类型,不能只打印包名。
4. 父子快照
try (PackageManagerLocal.UnfilteredSnapshot root = local.withUnfilteredSnapshot()) {
PackageManagerLocal.FilteredSnapshot appView =
root.filtered(appUid, UserHandle.of(userId));
try (appView) {
PackageState state = appView.getPackageState(packageName);
// state 仍由 root 的 Computer 支撑;root 关闭会使子视图失效。
}
}父快照关闭会使派生 filtered child 失效,因此 child 的 try-with-resources 不能替代 parent 的生命周期管理。反过来,child 关闭不会影响 parent。这个层级是排查“读取中途突然 closed”的关键:检查是否有更早返回路径关闭了 root。
5. 消费者边界
PMS 内部消费者大致分两类:需要完整包表的 system_server 服务使用 unfiltered view;代表应用或用户查询的服务使用 filtered view。ART、权限和数据清理代码经常在一次操作中取得多个 snapshot,若中间发生安装/卸载,两个 snapshot 之间可以观察到合法差异,这属于竞态窗口而不是自动的数据损坏。
6. 测试与命令
源码文件:frameworks/base/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
测试重点是 snapshot 关闭后访问失败、父子关闭传播和 filtered 查询的可见性断言。它们证明生命周期契约,不证明所有 PMS 写操作都在单个事务中完成。
rg -n "withFilteredSnapshot|withUnfilteredSnapshot|Snapshot already closed" frameworks/base/services/core/java/com/android/server/pm frameworks/base/services/tests/PackageManagerServiceTests
atest PackageManagerLocalSnapshotTest
adb shell dumpsys package snapshot-statistics如果两个服务看到不同 PackageState,先对齐 snapshot 创建时间、callingUid、userId 和 closed 状态,再检查 PMS 是否在两次读取之间发布了新 Computer。不要先修改 Settings 或清空 packages.xml;这些现象首先是 snapshot 语义问题。
