Skip to content

PackageManagerLocal快照诊断

从 PackageManagerLocal 的 snapshot 所有权、过滤身份和关闭边界出发,定位 system_server 内部包状态读取不一致问题。

基于android-17.0.0_r1
AndroidPMSPackageManagerLocalSnapshotDebugging

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

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

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

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. 父子快照 ​

java
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 写操作都在单个事务中完成。

bash
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 语义问题。