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
/**
* 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
/**
* 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
@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
// 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
@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
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
// 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
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
@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
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
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
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
@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
@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
@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
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
@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
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
/**
* 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
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
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
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
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
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
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
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,读取后 close | close 后读取抛 IllegalStateException | owned wrapper 生命周期 |
| 从 unfiltered 派生 filtered,再关闭 parent | child 读取抛异常 | parent-child 失效传播 |
| 创建 unowned filtered 后 close | wrapper 关闭,不释放传入 Computer | 所有权边界 |
| filtered 查询不可见 package | 返回 null/不在 map | visibility 过滤入口 |
| 注入 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
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 查询之间消失。测试应提供:
- package 在同一个 Computer 中存在,dump/clear 成功;
- package 在重新创建 filtered snapshot 后消失,
IllegalArgumentException被记录或按 flag 分支传播; - ART Local 未初始化时清理被安全跳过;
- unowned wrapper close 不影响传入 Computer 的其他消费者。
这些测试证明的是跨模块边界和失败处理,不等于证明文件系统 profile 清理已经成功;后者需要 ART/Installer 层测试。
11. 阅读路线
阅读一个新的 PackageManagerLocal 调用点时,按以下顺序追源码:
- 先找
LocalManagerRegistry.getManager()、getManagerOrThrow()或 injector 入口,确定 manager 是否已经注册。 - 看调用方选择
withUnfilteredSnapshot()、withFilteredSnapshot()还是withUnownedFilteredSnapshot()。 - 若是 filtered,记录 UID/user 来源,确认调用前是否清除了 Binder identity。
- 把所有读取放在一个 try-with-resources scope 内,确认 snapshot 没有跨线程或跨生命周期保存。
- 进入
PackageManagerLocalImpl.FilteredSnapshotImpl,检查单包和全量查询是否命中mUncommitPackageState、getPackageStateFiltered()或shouldFilterApplication()。 - 遇到 unowned,继续追底层 Computer 的 owner 和失效时机;wrapper close 不等于底层释放。
- 遇到 unfiltered,确认调用方确实需要 shared users、disabled system packages 或全量 package state,并检查它自己的授权逻辑。
- 最后读失败分支: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 只能减少读锁竞争,却不能替代所有写锁和跨服务同步。
