Skip to content

PackageManagerLocal

解析 PackageManagerLocal 的显式快照、过滤视图、所有权和 system_server 调用边界。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageManagerLocalSnapshotLocalManagerRegistry源码阅读

PackageManagerLocal ​

PackageManagerLocal 是 PMS 为 system_server 内部基础设施提供的新式本地入口。它和 PMS008 的 PackageManagerInternal 都不经过 Binder,但设计重点不同:PackageManagerInternal 是按方法暴露能力的兼容层,PackageManagerLocal 则要求调用方明确获取一个短生命周期 snapshot,再在这个 snapshot 上完成一组读取。

本文不把 PackageManagerLocal 简化成“去掉 Binder 的 PackageManager”。Android 17 的源码已经把几个重要约束写进接口和实现:

  • withUnfilteredSnapshot() 和 withFilteredSnapshot() 返回 AutoCloseable,快照使用范围必须显式结束;
  • UnfilteredSnapshot 可以派生多个不同身份的 FilteredSnapshot,但父快照关闭会使子快照失效;
  • withUnownedFilteredSnapshot() 可以借用已有 Computer,调用方不拥有它的释放责任;
  • filtered view 的每次状态访问都要经过 Computer 的 package visibility 和 user policy;
  • ART dexopt 场景还可以注入一个尚未提交的 PackageState,让查询看到安装提交前的状态;
  • LocalManagerRegistry 只负责发布和查找对象,不负责 snapshot 的生命周期或权限策略。

这篇文章的主线是一个快照对象从创建、读取、派生到关闭的完整生命周期,并用真实消费者说明为什么这些边界存在。PMS009 讲了 ComputerEngine 如何执行查询,PMS010 讲了 PMS 如何缓存和重建 Computer;本篇讲的是上层调用方如何安全地使用这个 Computer。

1. API边界 ​

1.1 接口注释 ​

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

java
/**
 * In-process API for server side PackageManager related infrastructure.
 *
 * For now, avoiding adding methods that rely on package data until we solve the snapshot
 * consistency problem.
 *
 * @hide
 */
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public interface PackageManagerLocal {

注释中的 “avoiding adding methods that rely on package data until we solve the snapshot consistency problem” 说明接口仍处于迁移阶段。它不是把所有 PMS 能力一次性搬到新 API,而是优先提供可以围绕 snapshot 组织的能力。@SystemApi(client = SYSTEM_SERVER) 也说明它面向 system_server 客户端,不是普通应用 API。

1.2 三种入口 ​

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

java
/**
 * Provides a snapshot scoped class to access snapshot-aware APIs. Should be short-term use and
 * closed as soon as possible.
 *
 * All reachable types in the snapshot are read-only.
 *
 * The snapshot assumes the caller is acting on behalf of the system and will not filter any
 * results.
 */
@NonNull
UnfilteredSnapshot withUnfilteredSnapshot();

/**
 * Infers the UID and user from the caller through Binder APIs.
 */
@NonNull
FilteredSnapshot withFilteredSnapshot();

/**
 * @param callingUid The caller UID to filter results based on.
 * @param user The user to query as.
 */
@NonNull
FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user);

/**
 * Same as {@link #withFilteredSnapshot()}, but from an existing snapshot computer.
 * The created object does not own the computer.
 */
@NonNull
FilteredSnapshot withUnownedFilteredSnapshot(@NonNull PackageDataSnapshot computer);

四个入口的差异可以归纳为两条轴:

入口过滤身份是否拥有底层 Computer
withUnfilteredSnapshot()system 可信视角,不过滤是
withFilteredSnapshot()从 Binder caller UID/user 推断是
withFilteredSnapshot(uid, user)调用方显式提供 UID/user是
withUnownedFilteredSnapshot(computer)当前 Binder caller UID/user否

“过滤身份”和“对象所有权”是两个独立概念。一个 filtered snapshot 可以拥有自己的 Computer,也可以借用已有 Computer;一个 unfiltered snapshot 也必须在使用结束时关闭,因为它拥有从 PMS 获取的 snapshot 引用。

1.3 两种视图 ​

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

java
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
interface UnfilteredSnapshot extends AutoCloseable {
    /**
     * Allows re-use of this snapshot, but in a filtered context.
     * Closing the parent snapshot closes any filtered children.
     */
    @NonNull
    FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user);

    @NonNull
    Map<String, PackageState> getPackageStates();

    @NonNull
    Map<String, SharedUserApi> getSharedUsers();

    @NonNull
    Map<String, PackageState> getDisabledSystemPackageStates();

    @Override
    void close();
}

@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
interface FilteredSnapshot extends AutoCloseable {
    @Nullable
    PackageState getPackageState(@NonNull String packageName);

    @NonNull
    Map<String, PackageState> getPackageStates();

    @Override
    void close();
}

接口故意没有给 FilteredSnapshot 暴露 shared users 和 disabled system package 全量 map。unfiltered view 用于 system_server 内部需要完整状态的迁移任务;filtered view 则只暴露按 caller/user 策略过滤后的 package state。调用方若需要 shared UID 的完整 group 状态,必须明确选择 unfiltered view,并承担更高的信任责任。

2. 注册与查找 ​

2.1 PMS注册 ​

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

java
// Expose private service for system components to use.
LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
LocalManagerRegistry.addManager(PackageManagerLocal.class,
        new PackageManagerLocalImpl(this));

PackageManagerInternal 通过 LocalServices 发布,PackageManagerLocal 通过 LocalManagerRegistry 发布。两者的注册表不同,所以不能使用 LocalServices.getService(PackageManagerLocal.class) 查找后者。

2.2 Registry错误 ​

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

java
@NonNull
private static final Map<Class<?>, Object> sManagers = new ArrayMap<>();

@Nullable
@SuppressWarnings("unchecked")
public static <T> T getManager(@NonNull Class<T> managerClass) {
    synchronized (sManagers) {
        return (T) sManagers.get(managerClass);
    }
}

@NonNull
public static <T> T getManagerOrThrow(@NonNull Class<T> managerClass)
        throws ManagerNotFoundException {
    T manager = getManager(managerClass);
    if (manager == null) {
        throw new ManagerNotFoundException(managerClass);
    }
    return manager;
}

public static <T> void addManager(@NonNull Class<T> managerClass, @NonNull T manager) {
    Objects.requireNonNull(managerClass, "managerClass");
    Objects.requireNonNull(manager, "manager");
    synchronized (sManagers) {
        if (sManagers.containsKey(managerClass)) {
            throw new IllegalStateException(managerClass.getName() + " is already registered");
        }
        sManagers.put(managerClass, manager);
    }
}

public static class ManagerNotFoundException extends Exception {
    public <T> ManagerNotFoundException(@NonNull Class<T> managerClass) {
        super("Local manager " + managerClass.getName() + " does not exist or is not ready");
    }
}

注册表只保证查找操作自身的线程安全和“只能注册一次”。ManagerNotFoundException 的消息把“不存在”和“尚未 ready”合并成一个失败原因;调用方不能据此推断 PMS 已经完成 package scan。getPackageManagerLocal() 又把 checked exception 包装成 unchecked runtime error:

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

java
public static @NonNull PackageManagerLocal getPackageManagerLocal() {
    try {
        return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class);
    } catch (ManagerNotFoundException e) {
        throw new RuntimeException(e);
    }
}

因此系统服务必须在正确的 SystemServer 阶段获取 manager。若在 PMS 注册之前调用 helper,失败表现是 unchecked exception,而不是一个可用的空对象。

2.3 启动顺序 ​

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

java
// DexUseManagerLocal needs to be loaded after PackageManagerLocal has been registered, but
// before PackageManagerService is started.

这条注释说明注册时机是跨服务依赖图的一部分:有些本地 manager 必须在 PMS 注册后、某些服务启动前加载。文章阅读中遇到 LocalManagerRegistry.getManager(PackageManagerLocal.class),应继续回到 SystemServer 启动顺序,不能只看调用方构造函数。

3. 实现入口 ​

3.1 创建快照 ​

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

java
public class PackageManagerLocalImpl implements PackageManagerLocal {
    private final PackageManagerService mService;

    public PackageManagerLocalImpl(PackageManagerService service) {
        mService = service;
    }

    @NonNull
    @Override
    public UnfilteredSnapshotImpl withUnfilteredSnapshot() {
        return new UnfilteredSnapshotImpl(
                mService.snapshotComputer(false /*allowLiveComputer*/));
    }

    @NonNull
    @Override
    public FilteredSnapshotImpl withFilteredSnapshot() {
        return withFilteredSnapshot(Binder.getCallingUid(), Binder.getCallingUserHandle());
    }

    @NonNull
    @Override
    public FilteredSnapshotImpl withFilteredSnapshot(int callingUid,
            @NonNull UserHandle user) {
        return withFilteredSnapshot(callingUid, user, /* uncommittedPs= */ null);
    }

    @NonNull
    private FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user,
            @Nullable PackageState uncommittedPs) {
        return new FilteredSnapshotImpl(callingUid, user,
                mService.snapshotComputer(/* allowLiveComputer= */ false),
                /* parentSnapshot= */ null, uncommittedPs, /* unowned= */ false);
    }
}

这里最重要的一行是 snapshotComputer(false): PackageManagerLocal 不接受 live computer,即使当前线程偶然持有 PMS mLock 也必须获取时间点 snapshot。这样 Local API 的调用方可以把 snapshot 作为短期只读对象跨越自己的业务逻辑,而不会悄悄绑定 PMS live state。

3.2 身份保存 ​

无参数 withFilteredSnapshot() 使用 Binder.getCallingUid() 和 Binder.getCallingUserHandle()。如果调用方先 Binder.clearCallingIdentity() 再创建 snapshot,就会把过滤身份改成 system_server 自身;正确做法是先保存原始 UID,或者使用显式 (callingUid, user) 重载。

这与 PMS008 的 PackageManagerInternal 一致:本地 API 不会自动恢复一份“原始远端身份”。PackageManagerLocal 只是把身份绑定在 snapshot 对象里,避免每次 getPackageState() 都重复传参。

3.3 SDK数据 ​

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

java
@Override
public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
        @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
        @NonNull String seInfo, int flags) throws IOException {
    mService.reconcileSdkData(volumeUuid, packageName, subDirNames, userId, appId,
            previousAppId, seInfo, flags);
}

接口虽然名为 Local,但并非所有方法都返回 snapshot。reconcileSdkData() 是一个可能触及 Installer/文件系统的写路径,直接委托 PMS,并保留 IOException。调用方不能把 PackageManagerLocal 的所有方法都当成无锁纯读。

4. 生命周期与所有权 ​

4.1 关闭状态 ​

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

java
private abstract static class BaseSnapshotImpl implements AutoCloseable {
    private boolean mClosed;

    @NonNull
    protected Computer mSnapshot;

    // True if this object does not own the computer and is not responsible for releasing
    // the resources that the computer holds.
    private final boolean mUnowned;

    private BaseSnapshotImpl(@NonNull PackageDataSnapshot snapshot, boolean unowned) {
        mSnapshot = (Computer) snapshot;
        mUnowned = unowned;
    }

    @CallSuper
    @Override
    public void close() {
        mClosed = true;
        if (mUnowned) {
            return;
        }
        mSnapshot = null;
        // TODO: Recycle snapshots?
    }

    @CallSuper
    protected void checkClosed() {
        if (mClosed) {
            throw new IllegalStateException("Snapshot already closed");
        }
    }
}

close() 的当前实现主要释放 Java 对象对 Computer 的引用,并设置 mClosed。它不是给 PMS 发送“删除缓存 snapshot”的命令;PMS 的 sSnapshot 生命周期由 PMS 自己管理。TODO: Recycle snapshots? 也说明当前没有对象池回收协议。

任何公开读取方法都先 checkClosed()。因此 try-with-resources 不只是编码风格,而是 API contract:关闭后继续读取必须失败,避免调用方误以为 snapshot 仍然代表原来的视图。

4.2 Unowned边界 ​

mUnowned 为真时,close() 不会把 mSnapshot 置空。当前代码即使继续执行也只会丢引用,但作者选择提前返回,以防未来 close 逻辑增加资源释放或状态修改后误伤借用者。

这是一条所有权边界:借用 snapshot 的 wrapper 可以结束自己的使用,但不能负责释放底层 Computer,也不能让底层对象对其他借用者失效。

4.3 派生子对象 ​

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

java
private static class UnfilteredSnapshotImpl extends BaseSnapshotImpl
        implements UnfilteredSnapshot {
    private final Map<String, PackageState> mCachedUnmodifiablePackageStates;
    private final Map<String, SharedUserApi> mCachedUnmodifiableSharedUsers;

    private UnfilteredSnapshotImpl(@NonNull PackageDataSnapshot snapshot) {
        super(snapshot, /* unowned= */ false);
    }

    @Override
    public FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user) {
        return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this,
                /* uncommittedPs= */ null, /* unowned= */ true);
    }
}

子 filtered snapshot 使用父对象的 mSnapshot,并标记 unowned = true。父对象负责底层 Computer,子对象只负责自己的 UID/user 过滤和缓存。父关闭时,子通过 mParentSnapshot.checkClosed() 一起失效:

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

java
private static class FilteredSnapshotImpl extends BaseSnapshotImpl
        implements FilteredSnapshot {
    @Nullable
    private final UnfilteredSnapshotImpl mParentSnapshot;

    @Override
    protected void checkClosed() {
        if (mParentSnapshot != null) {
            mParentSnapshot.checkClosed();
        }
        super.checkClosed();
    }
}

4.4 Map缓存 ​

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

java
@NonNull
@Override
public Map<String, PackageState> getPackageStates() {
    checkClosed();

    if (mCachedUnmodifiablePackageStates == null) {
        mCachedUnmodifiablePackageStates =
                Collections.unmodifiableMap(mSnapshot.getPackageStates());
    }
    return mCachedUnmodifiablePackageStates;
}

@Override
public void close() {
    super.close();
    mCachedUnmodifiablePackageStates = null;
    mCachedUnmodifiableDisabledSystemPackageStates = null;
}

首次访问才把 Computer 返回的 map 包装成 unmodifiable map,重复访问复用 wrapper。关闭时清空本地缓存,避免 wrapper 长期持有已关闭 snapshot 的 map。Collections.unmodifiableMap() 只禁止调用方通过该 view 修改 map,并不把 map 中的 PackageState 变成可变安全对象;接口另有 “all reachable types are read-only” 的整体 contract。

5. Filtered查询 ​

5.1 单包查询 ​

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

java
@Nullable
@Override
public PackageState getPackageState(@NonNull String packageName) {
    checkClosed();
    if (mUncommitPackageState != null
            && packageName.equals(mUncommitPackageState.getPackageName())) {
        return mUncommitPackageState;
    }
    return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
}

单包查询的顺序是:检查 snapshot 是否关闭→优先匹配未提交状态→否则进入 Computer.getPackageStateFiltered()。过滤策略、重命名包处理、user state 和 visibility 都由 Computer 继续执行,FilteredSnapshot 不复制这些规则。

5.2 全量查询 ​

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

java
@NonNull
@Override
public Map<String, PackageState> getPackageStates() {
    checkClosed();

    if (mFilteredPackageStates == null) {
        var packageStates = mSnapshot.getPackageStates();
        var filteredPackageStates = new ArrayMap<String, PackageState>();
        for (int index = 0, size = packageStates.size(); index < size; index++) {
            var packageState = packageStates.valueAt(index);
            if (mUncommitPackageState != null
                    && packageState.getPackageName().equals(
                    mUncommitPackageState.getPackageName())) {
                packageState = (PackageStateInternal) mUncommitPackageState;
            }
            if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
                filteredPackageStates.put(packageStates.keyAt(index), packageState);
            }
        }
        mFilteredPackageStates = Collections.unmodifiableMap(filteredPackageStates);
    }

    return mFilteredPackageStates;
}

和 getPackageState() 相比,全量 map 有两个成本:它遍历 snapshot 中所有 package state,并对每个状态执行 shouldFilterApplication();因此接口 Javadoc 明确建议能按包名查询时优先使用单包方法。

未提交状态替换发生在过滤前。这样 dexopt 等调用方可以把新 package state 放进与现有包集合相同的过滤流程,而不是先得到一份过滤结果再手工拼接。

5.3 身份与快照 ​

FilteredSnapshotImpl 保存两个字段:

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

java
private final int mCallingUid;

@UserIdInt
private final int mUserId;

private FilteredSnapshotImpl(int callingUid, @NonNull UserHandle user,
        @NonNull PackageDataSnapshot snapshot,
        @Nullable UnfilteredSnapshotImpl parentSnapshot,
        @Nullable PackageState uncommittedPs, boolean unowned) {
    super(snapshot, unowned);
    mCallingUid = callingUid;
    mUserId = user.getIdentifier();
    mParentSnapshot = parentSnapshot;
    mUncommitPackageState = uncommittedPs;
}

底层 Computer 何时构建由 PMS 决定;mCallingUid 和 mUserId 只决定当前 wrapper 如何过滤。相同一个 Computer 可以派生多个不同身份的 filtered snapshot,避免为每个 caller 重建 package 数据。

6. Unowned与pending ​

6.1 借用Computer ​

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

java
@Override
public FilteredSnapshot withUnownedFilteredSnapshot(@NonNull PackageDataSnapshot computer) {
    if (Flags.alternativeForDexoptCleanup()) {
        return new FilteredSnapshotImpl(Binder.getCallingUid(), Binder.getCallingUserHandle(),
                computer,
                /* parentSnapshot= */ null, /* uncommittedPs= */ null,
                /* unowned= */ true);
    } else {
        return withFilteredSnapshot();
    }
}

该方法的参数类型是 PackageDataSnapshot,实现再强制转换为 Computer。这说明 Computer 是当前实现要求的具体 snapshot 能力,而 PackageDataSnapshot 是跨模块传递的较小契约。调用方传入的对象必须确实由 PMS/PackageManagerLocal 提供,否则会在实现中发生类型错误。

flag 为 false 时,Android 17 仍回退到重新创建 filtered snapshot;flag 为 true 时才使用调用方传入的 Computer。这个条件分支与 ART dexopt cleanup 迁移相关,不能把 withUnownedFilteredSnapshot() 解释成无条件零拷贝。

6.2 ART调用 ​

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

java
public static void dumpDexoptState(@NonNull IndentingPrintWriter ipw,
        @NonNull PackageDataSnapshot computer, @Nullable String packageName) {
    try (PackageManagerLocal.FilteredSnapshot snapshot =
            getPackageManagerLocal().withUnownedFilteredSnapshot(computer)) {
        if (packageName != null) {
            if (Flags.alternativeForDexoptCleanup()) {
                // The caller has already vetted the package name against the computer, so
                // IllegalArgumentException cannot happen.
                DexOptHelper.getArtManagerLocal().dumpPackage(ipw, snapshot, packageName);
            } else {
                try {
                    DexOptHelper.getArtManagerLocal().dumpPackage(ipw, snapshot, packageName);
                } catch (IllegalArgumentException e) {
                    // Package isn't found, but that should only happen due to race.
                    ipw.println(e);
                }
            }
        } else {
            DexOptHelper.getArtManagerLocal().dump(ipw, snapshot);
        }
    }
}

这个调用把已有 PackageDataSnapshot 传给 PackageManagerLocal,再用 try-with-resources 只管理 wrapper。注释中的 race 说明:如果 fallback 路径重新创建 snapshot,package 在 caller 预检查后仍可能消失,所以 dumpPackage() 需要捕获 IllegalArgumentException。快照一致性降低了竞争窗口,但不等于跨调用的全局事务。

6.3 pending入口 ​

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

java
/**
 * Creates a {@link FilteredSnapshot} with a uncommitted {@link PackageState} that is used for
 * dexopt in the art service to get the correct package state before the package is committed.
 */
public static FilteredSnapshotImpl withFilteredSnapshot(
        PackageManagerLocal pm, @NonNull PackageState uncommittedPs) {
    return ((PackageManagerLocalImpl) pm).withFilteredSnapshot(
            Binder.getCallingUid(), Binder.getCallingUserHandle(), uncommittedPs);
}

该方法不是接口方法,而是实现类的静态 helper。它要求传入实际的 PackageManagerLocalImpl,并使用当前 Binder caller 的 UID/user。未提交 state 只在 package name 匹配时覆盖 snapshot 中的同名状态,其他 package 仍来自原 snapshot。

7. 真实消费者 ​

7.1 StorageStats ​

源码文件:frameworks/base/services/usage/java/com/android/server/usage/StorageStatsService.java

java
private void computeAppArtStats(PackageStats stats, String packageName) {
    ArtManagedFileStats artManagedFileStats;
    try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
        artManagedFileStats =
                getArtManagerLocal().getArtManagedFileStats(snapshot, packageName);
    }

    stats.dexoptSize += artManagedFileStats
            .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT);
    stats.refProfSize += artManagedFileStats
            .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_REF_PROFILE);
    stats.curProfSize += artManagedFileStats
            .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_CUR_PROFILE);
}

StorageStats 只把 snapshot 传给 ART 查询,并在返回后关闭。PackageManagerLocal 不负责 ART 文件统计;它只提供一个按 caller/user 过滤的 package state 视图。ART owner 决定 dexopt、reference profile 和 current profile 的大小,StorageStats owner 决定如何累加到 PackageStats。

7.2 AppDataHelper ​

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

java
void destroyAppProfilesLIF(String packageName) {
    if (!DexOptHelper.artManagerLocalIsInitialized()) {
        // This function may get called while PackageManagerService is constructed ...
        // ART Service hasn't yet been started then ... We can skip clearing stale profiles.
        return;
    }

    try (PackageManagerLocal.FilteredSnapshot snapshot =
            getPackageManagerLocal().withFilteredSnapshot()) {
        try {
            DexOptHelper.getArtManagerLocal().clearAppProfiles(snapshot, packageName);
        } catch (IllegalArgumentException e) {
            // Package isn't found, but that should only happen due to race.
            Slog.w(TAG, e);
        }
    }
}

这里有两个启动边界:ART Local 尚未初始化时直接跳过清理;ART 已初始化后才创建 filtered snapshot。注释还说明 stale profile 会被 ART service/runtime 忽略,因此跳过是可接受的降级,而不是把 Local manager 未就绪当作 fatal error。

7.3 DevicePolicy ​

源码文件:frameworks/base/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java

java
try (var snapshot = mInjector.getPackageManagerLocal().withUnfilteredSnapshot()) {
    final Map<String, PackageState> packageStates = snapshot.getPackageStates();
    // Device policy code evaluates package state for the target user.
}

DevicePolicy 的多个路径使用 unfiltered snapshot,因为策略 owner 需要跨包比较、设备/配置 owner 关系或 disabled system package 状态。真正的 user policy 仍由 DevicePolicyManagerService 自己执行;unfiltered 只表示 Local 层不做 AppsFilter visibility,不等于跳过 DevicePolicy 的授权判断。

7.4 权限迁移 ​

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

java
PackageManagerLocal packageManagerLocal =
        LocalManagerRegistry.getManager(PackageManagerLocal.class);
try (PackageManagerLocal.UnfilteredSnapshot snapshot =
        packageManagerLocal.withUnfilteredSnapshot()) {
    Map<String, SharedUserApi> sharedUsers = snapshot.getSharedUsers();
    // Migrate permission state using the stable snapshot view.
}

权限迁移需要 shared UID group 的完整 map,因此选择 unfiltered snapshot。SharedUserApi 是只读接口,PermissionMigrationHelper 不直接修改 SharedUserSetting;迁移后的持久化和权限 owner 仍由权限系统负责。

7.5 AppOps批处理 ​

源码文件:frameworks/base/services/core/java/com/android/server/appop/AppOpsService.java

java
try (PackageManagerLocal.UnfilteredSnapshot snapshot =
        getPackageManagerLocal().withUnfilteredSnapshot()) {
    for (int i = 0; i < packageNames.size(); i++) {
        PackageState state = snapshot.getPackageStates().get(packageNames.get(i));
        if (state == null) {
            continue;
        }
        updateUidState(state.getAppId(), state.getPackageName());
    }
}

批处理内复用同一个 unfiltered snapshot,避免对每个 package 重新获取 Computer。AppOps 的 UID mode 和运行时状态不属于 PackageManagerLocal;Local snapshot 只提供一致的 package identity/state 输入。

8. 与旧入口的差异 ​

8.1 Internal代理 ​

旧入口的便利代理类似:

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

java
public final PackageInfo getPackageInfo(String packageName,
        long flags, int filterCallingUid, int userId) {
    return snapshot().getPackageInfoInternal(packageName,
            PackageManager.VERSION_CODE_HIGHEST, flags, filterCallingUid, userId);
}

每次方法调用都可能重新选择 Computer。如果业务连续调用多个 PMI 方法,必须自己意识到它们可能跨 snapshot 版本。

8.2 Local作用域 ​

新入口的典型使用示例是(调用方代码):

相关源码:frameworks/base/services/core/java/com/android/server/pm/PackageManagerLocal.java

java
try (PackageManagerLocal.FilteredSnapshot snapshot =
        packageManagerLocal.withFilteredSnapshot(callingUid, UserHandle.of(userId))) {
    PackageState state = snapshot.getPackageState(packageName);
    Map<String, PackageState> visible = snapshot.getPackageStates();
    // 在同一个 snapshot 视图内完成业务读取。
}

作用域内的多次读取共享一个 Computer 和一组过滤身份。它不保证外部服务状态与 PMS 完全同刻,也不允许写回 package state,但能避免便利代理在一次业务中不知不觉跨版本。

8.3 并存原因 ​

Android 17 仍有大量 PackageManagerInternal 调用方,旧接口的迁移成本很高;PackageManagerLocal 又需要调用方适配 try-with-resources、选择 filtered/unfiltered,并处理 close 之后的异常。因此新架构不是立即删除旧入口,而是先在需要一致 snapshot 的新代码中建立更清晰的 contract。

9. 失败与边界 ​

9.1 manager 未注册 ​

调用 getPackageManagerLocal() 早于 PMS 注册会抛 RuntimeException,底层 cause 是 ManagerNotFoundException。这通常意味着启动顺序错误或测试没有安装本地 manager,不应通过创建一个 fake empty manager 静默吞掉。

9.2 snapshot 已关闭 ​

所有读取方法都会调用 checkClosed(),关闭后抛 IllegalStateException("Snapshot already closed")。如果 filtered snapshot 有 parent,还会先检查 parent;因此子对象可能因为父对象关闭而失败,即使子对象自己的 mClosed 仍为 false。

9.3 空结果语义 ​

getPackageState() 返回 null 可能来自:package 不存在、用户未安装、caller 不可见、package 被过滤或 snapshot 中只有未匹配的状态。调用方不能把 null 直接解释为磁盘上没有 APK,也不能绕过 filtered view 改用 unfiltered view 来“确认存在”,除非它确实拥有 system-level 业务授权。

9.4 全量过滤的成本 ​

FilteredSnapshot.getPackageStates() 会遍历整个 snapshot 并逐项调用 visibility 策略。频繁调用会重复构建 filtered map;实现只在同一个 wrapper 内缓存,关闭后缓存丢弃。大批量业务应在一个 snapshot scope 内完成,能按名称查询时不要先取全量 map。

9.5 Unowned竞态 ​

借用已有 Computer 可以让多个服务共享同一时间点视图,但调用方必须确认该 Computer 的生命周期覆盖整个借用 scope。withUnownedFilteredSnapshot() 不会延长底层对象寿命,也不会阻止原 owner 结束自己的使用。Android 17 当前实现的 Computer 通常由 Java 引用保持存活,但 API contract 仍要求借用者不承担释放责任。

10. 测试与证明范围 ​

10.1 生命周期测试 ​

针对 PackageManagerLocalImpl,测试应覆盖以下输入和断言:

输入/动作断言证明范围
创建 owned unfiltered,读取后 closeclose 后读取抛 IllegalStateExceptionowned wrapper 生命周期
从 unfiltered 派生 filtered,再关闭 parentchild 读取抛异常parent-child 失效传播
创建 unowned filtered 后 closewrapper 关闭,不释放传入 Computer所有权边界
filtered 查询不可见 package返回 null/不在 mapvisibility 过滤入口
注入 uncommitted state同名 package 返回注入对象dexopt pre-commit 语义
重复读取 package map返回同一个 wrapper/cache 结果lazy cache 行为

10.2 现有消费者测试 ​

测试文件:frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java

相关源码:frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java

java
try (var snapshot = dpms.mMockInjector.getPackageManagerLocal().withUnfilteredSnapshot()) {
    // Device policy assertions use the snapshot-scoped package view.
}

该测试使用 mock injector 提供 PackageManagerLocal,再在 try-with-resources 中执行 DevicePolicy 断言。它能证明 DPMS 正确关闭 snapshot、按 unfiltered contract 读取状态;不能证明真实 LocalManagerRegistry 注册顺序、PMS snapshot 重建或 AppsFilter 算法。

10.3 ART测试 ​

DexOptHelper 和 AppDataHelper 的代码显示,package 可能在调用方预检查和 ART 查询之间消失。测试应提供:

  1. package 在同一个 Computer 中存在,dump/clear 成功;
  2. package 在重新创建 filtered snapshot 后消失,IllegalArgumentException 被记录或按 flag 分支传播;
  3. ART Local 未初始化时清理被安全跳过;
  4. unowned wrapper close 不影响传入 Computer 的其他消费者。

这些测试证明的是跨模块边界和失败处理,不等于证明文件系统 profile 清理已经成功;后者需要 ART/Installer 层测试。

11. 阅读路线 ​

阅读一个新的 PackageManagerLocal 调用点时,按以下顺序追源码:

  1. 先找 LocalManagerRegistry.getManager()、getManagerOrThrow() 或 injector 入口,确定 manager 是否已经注册。
  2. 看调用方选择 withUnfilteredSnapshot()、withFilteredSnapshot() 还是 withUnownedFilteredSnapshot()。
  3. 若是 filtered,记录 UID/user 来源,确认调用前是否清除了 Binder identity。
  4. 把所有读取放在一个 try-with-resources scope 内,确认 snapshot 没有跨线程或跨生命周期保存。
  5. 进入 PackageManagerLocalImpl.FilteredSnapshotImpl,检查单包和全量查询是否命中 mUncommitPackageState、getPackageStateFiltered() 或 shouldFilterApplication()。
  6. 遇到 unowned,继续追底层 Computer 的 owner 和失效时机;wrapper close 不等于底层释放。
  7. 遇到 unfiltered,确认调用方确实需要 shared users、disabled system packages 或全量 package state,并检查它自己的授权逻辑。
  8. 最后读失败分支:manager 未就绪、snapshot closed、package race、ART 未初始化和 user visibility 各由不同 owner 处理。

小结 ​

Android 17 的 PackageManagerLocal 把 PMS 内部读取从“每次调用一个方法”推进到“在明确 scope 内持有一个只读视图”:

  • LocalManagerRegistry 只负责 manager 的发布和查找;PMS 注册顺序与 system_server 启动依赖必须单独追踪。
  • withUnfilteredSnapshot() 提供 system 可信全量视图,withFilteredSnapshot() 把 UID/user 绑定到 wrapper,withUnownedFilteredSnapshot() 借用已有 Computer 但不承担释放责任。
  • BaseSnapshotImpl 用 close 状态阻止过期读取;父 unfiltered 关闭会通过 checkClosed() 使派生 filtered 子对象失效。
  • filtered 单包查询走 getPackageStateFiltered(),全量查询遍历所有 state 并逐项执行 visibility 策略;能按包名查时应避免全量 map。
  • 未提交 PackageState 是 ART dexopt 在安装提交前观察正确 package state 的专门机制,不是普通调用方的通用覆盖 API。
  • Local snapshot 只保证 PMS package data 的 snapshot 视图;ART、Installer、DevicePolicy、AppOps 等外部状态仍由各自 owner 管理,package 消失和服务未初始化都必须有独立失败路径。

下一篇 PMS012 将把这些 snapshot 调用放回 PMS 的锁体系,解释 mLock、mInstallLock、mPackageStateWriteLock 和 helper 锁之间的顺序,以及为什么 snapshot 只能减少读锁竞争,却不能替代所有写锁和跨服务同步。