PackageManagerInternal
PackageManagerInternal 是 Package Manager 在 system_server 内部发布的本地接口。它和上一文的 IPackageManager 同时存在,却解决两个不同问题:IPackageManager 把应用或其他进程带到 PMS,PackageManagerInternal 则让 ActivityTaskManager、PermissionPolicy、WindowManager 等同进程服务直接访问 PMS 的状态和策略。
这篇文章只讨论 Android 17 的真实源码。重点不是把接口中的方法名抄成一张表,而是沿着几条可运行的主线回答以下问题:接口何时注册、调用方拿到的到底是什么、谁负责身份和用户过滤、一次查询使用哪个 snapshot、observer 如何从快照变成持续通知、状态 mutation 如何处理并发冲突,以及返回成功时哪些后续工作仍未完成。
阅读前建议先掌握 PMS 的 Binder 接口 中的 IPackageManager/Computer 关系。本文不会重复 AIDL transaction 的解析;两者的边界会在第一节用源码重新钉住。
1. 边界
1.1 接口声明
源码文件:frameworks/base/services/core/java/android/content/pm/PackageManagerInternal.java
/**
* Package manager local system service interface.
*
* @hide Only for use within the system server.
*/
public abstract class PackageManagerInternal {这里有三个容易被忽略的事实。
第一,类位于 services/core/java,不是给普通 SDK 应用编译的公开 API。第二,local system service 表示它通过 LocalServices 在同一个 system_server 进程内查找。第三,抽象类本身没有 Stub、Proxy、Parcel 或 RemoteException;调用是普通 Java 虚调用,参数和返回值也没有跨进程序列化。
这并不意味着“没有安全问题”。Binder 入口可以依赖 Binder.getCallingUid() 得到内核提供的远端身份,而本地调用没有这层自动语义。接口因此把 filterCallingUid、callingUid、callingPid、userId 等上下文显式放进方法签名,调用方必须选择正确的身份语境。
图中两条箭头不能互换:应用进程走 IPackageManager,同进程服务走 PackageManagerInternal。如果在内部服务里为了方便改用公开 PackageManager,就会重新经过 Binder facade,丢失本地接口暴露的内部对象和明确 snapshot 语义。
1.2 两套本地入口
PMS 在同一个初始化段同时发布两个内部入口:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
// Create sub-components that provide services / data. Order here is important.
t.traceBegin("createSubComponents");
// Expose private service for system components to use.
LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
LocalManagerRegistry.addManager(PackageManagerLocal.class,
new PackageManagerLocalImpl(this));PackageManagerInternal 是按方法提供能力的老接口;PackageManagerLocal 则由 LocalManagerRegistry 管理,强调显式取得一个 snapshot-scoped 访问对象。Android 17 仍有大量旧调用方依赖 PackageManagerInternal,所以两套入口并存。阅读源码时要先确认调用方拿的是哪一个,不能把 PackageManagerInternal.snapshot() 和 PackageManagerLocal.snapshot() 当成同一层 API。
2. 注册时机
2.1 依赖顺序
同一构造函数继续创建 UserManager、ComponentResolver、PermissionManager、Settings 和其他 helper:
源码文件: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));
LocalServices.addService(TestUtilityService.class, this);
mTestUtilityService = LocalServices.getService(TestUtilityService.class);
mUserManager = injector.getUserManagerService();
mUserNeedsBadging = new UserNeedsBadgingCache(mUserManager);
mComponentResolver = injector.getComponentResolver();
mPermissionManager = injector.getPermissionManagerServiceInternal();
mSettings = injector.getSettings();注释明确说 Order here is important。注册对象已经持有 PackageManagerService.this,但它的若干 helper 由后续字段提供。PackageManagerInternalImpl 通过 protected getter 延迟读取这些字段,因此“接口已经可查找”与“每个方法都已经可调用”不是同一个时间点。
真正的依赖关系可以在 injector 生产者里看到:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
(i, pm) -> AppsFilterImpl.create(i,
i.getLocalService(PackageManagerInternal.class)),AppsFilterImpl 的生产者需要从 injector 取 PackageManagerInternal。如果 PMS 把本地服务注册推迟到所有子组件之后,这条依赖就会在启动阶段拿到 null 或触发错误顺序。因此,注册时机是启动依赖图的一部分,不是形式上的“发布 API”。
2.2 ready阶段
另一个典型消费者是 PermissionPolicyService。它在 onStart() 中查找本地接口,然后注册 package observer:
源码文件:frameworks/base/services/core/java/com/android/server/policy/PermissionPolicyService.java
@Override
public void onStart() {
mPackageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
mPermissionManagerInternal = LocalServices.getService(
PermissionManagerServiceInternal.class);
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManagerInternal.getPackageList(new PackageListObserver() {
@Override
public void onPackageAdded(String packageName, int appId) {
final int[] userIds = LocalServices.getService(UserManagerInternal.class)
.getUserIds();
for (final int userId : userIds) {
if (isStarted(userId)) {
grantDefaultPermissionsToPackage(packageName, userId);
}
}
}
});
}onStart() 阶段完成的是引用获取和 observer 注册;默认权限授予仍由后续 package 事件和 user 初始化状态驱动。不能因为 getPackageList() 返回了当前列表,就推断所有用户权限同步已经完成。
3. 三层实现
3.1 Base职责
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
/**
* Internal manager variant of {@link IPackageManagerBase}. See that class for info.
* {@link PackageManagerInternal} should eventually passing in a snapshot instance, deprecating
* this class, but that requires much larger refactor.
*/
abstract class PackageManagerInternalBase extends PackageManagerInternal {
@NonNull
private final PackageManagerService mService;
public PackageManagerInternalBase(@NonNull PackageManagerService service) {
mService = service;
}
@NonNull protected abstract Context getContext();
@NonNull protected abstract PermissionManagerServiceInternal getPermissionManager();
@NonNull protected abstract AppDataHelper getAppDataHelper();
@NonNull protected abstract PackageObserverHelper getPackageObserverHelper();
@NonNull protected abstract ResolveIntentHelper getResolveIntentHelper();
@NonNull protected abstract SuspendPackageHelper getSuspendPackageHelper();
@NonNull protected abstract DistractingPackageHelper getDistractingPackageHelper();
@NonNull protected abstract ProtectedPackages getProtectedPackages();
@NonNull protected abstract UserNeedsBadgingCache getUserNeedsBadging();
@NonNull protected abstract InstantAppRegistry getInstantAppRegistry();
@NonNull protected abstract ApexManager getApexManager();Base 不是一个空的抽象适配器。它保存 PMS 引用,同时把 Context、权限、解析、暂停包、保护包、instant app、APEX 等 owner 以 getter 形式交给具体实现。这样简单方法不需要在接口层暴露 PMS 的全部字段,而复杂方法仍可以在 PackageManagerService.IPackageManagerImpl 中选择正确的锁和 helper。
3.2 snapshot粒度
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@NonNull
@Override
public final Computer snapshot() {
return mService.snapshotComputer();
}
@Override
@Deprecated
public final PackageInfo getPackageInfo(String packageName,
@PackageManager.PackageInfoFlagsBits long flags, int filterCallingUid, int userId) {
return snapshot().getPackageInfoInternal(packageName,
PackageManager.VERSION_CODE_HIGHEST, flags, filterCallingUid, userId);
}getPackageInfo() 的调用链很短:取一个 Computer,固定版本参数为 VERSION_CODE_HIGHEST,把 flags、过滤 UID 和用户传给 snapshot。final 防止子类改写这个简单代理;@Deprecated 是迁移信号,表示新代码应该倾向于显式持有 snapshot,而不是说返回结果不再可用。
关键限制是“每次方法调用自动取一次 snapshot”。下面的代码会取两次:
AndroidPackage pkg = pmi.getPackage("com.example.app");
PackageStateInternal state = pmi.getPackageStateInternal("com.example.app");如果两次调用之间发生包更新,两个返回对象可能来自不同版本。需要原子地观察多个对象时,应先拿 Computer snapshot = pmi.snapshot(),再沿同一 snapshot 查询。PackageManagerInternal 的便利代理不能替代业务级 snapshot 设计。
3.3 Impl职责
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@VisibleForTesting
class PackageManagerInternalImpl extends PackageManagerInternalBase {
public PackageManagerInternalImpl() {
super(PackageManagerService.this);
}
@NonNull
@Override
protected Context getContext() {
return mContext;
}
@NonNull
@Override
protected PermissionManagerServiceInternal getPermissionManager() {
return mPermissionManager;
}
@NonNull
@Override
protected PackageObserverHelper getPackageObserverHelper() {
return mPackageObserverHelper;
}
@NonNull
@Override
protected ResolveIntentHelper getResolveIntentHelper() {
return mResolveIntentHelper;
}
}这个类把“谁拥有状态”说得很清楚:权限状态归 mPermissionManager,observer 集合归 mPackageObserverHelper,Intent 解析归 mResolveIntentHelper,Context 和资源归 PMS 的 mContext。PackageManagerInternal 只是契约入口,不应该被误读成所有状态的 owner。
4. 身份语境
4.1 显式UID
源码文件:frameworks/base/services/core/java/android/content/pm/PackageManagerInternal.java
/**
* Retrieve all of the information we know about a particular package/application.
* @param filterCallingUid The results will be filtered in the context of this UID instead
* of the calling UID.
*/
public abstract PackageInfo getPackageInfo(String packageName,
@PackageManager.PackageInfoFlagsBits long flags, int filterCallingUid, int userId);同样的设计出现在 getApplicationInfo()、getActivityInfo()、queryIntentActivities() 和 queryIntentReceivers()。这里的 UID 不是“仅用于日志”的参数,而是决定 package visibility 的输入。Computer 会把它传给过滤逻辑,调用方如果填入错误 UID,就可能把结果过滤得过窄或放得过宽。
4.2 查询转发
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@Override
@Deprecated
public final ApplicationInfo getApplicationInfo(String packageName,
@PackageManager.ApplicationInfoFlagsBits long flags, int filterCallingUid, int userId) {
return snapshot().getApplicationInfoInternal(packageName, flags, filterCallingUid, userId);
}
@Override
@Deprecated
public final ActivityInfo getActivityInfo(ComponentName component,
@PackageManager.ComponentInfoFlagsBits long flags, int filterCallingUid, int userId) {
return snapshot().getActivityInfoInternal(component, flags, filterCallingUid, userId);
}
@Override
@Deprecated
public final boolean canQueryPackage(int callingUid, @Nullable String packageName) {
return snapshot().canQueryPackage(callingUid, packageName);
}Base 没有在这里重新实现 visibility 规则,而是把 UID 交给 Computer。这让包可见性、用户状态和 instant app 规则继续由同一套 snapshot 逻辑处理,也避免本地 API 与 Binder API 各自维护一份容易漂移的过滤算法。
4.3 SYSTEM_UID
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@Override
@Deprecated
public final int getPackageUid(String packageName,
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
return snapshot().getPackageUidInternal(packageName, flags, userId, Process.SYSTEM_UID);
}这里没有 filterCallingUid 参数,而是固定使用 Process.SYSTEM_UID。这是一个明确的内部 API 约定:该方法要求按系统可信视角做直接 UID 查找。它不能被推广成“所有 PackageManagerInternal 查询都应使用 SYSTEM_UID”;需要代表具体服务或应用视角的 API,必须调用带 callingUid/filterCallingUid 的变体。
另一个容易混淆的例子是 isPackageAppLockEnabled():
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public boolean isPackageAppLockEnabled(String packageName, int userId) {
if (!android.security.Flags.appLockApis()) {
return false;
}
final Computer snapshot = snapshotComputer();
// Use Process.myUid here because this is the PackageManagerInternal implementation, it
// isn't called outside of system_server
return mAppLockPackageHelper.isPackageAppLockEnabled(snapshot, packageName, userId,
Process.myUid());
}注释给出了选择理由:实现不会被 system_server 之外调用,所以使用本进程 UID。这个结论来自调用边界和源码注释,而不是“内部接口天然安全”。
5. Snapshot链
5.1 snapshotComputer
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
/**
* Return the cached computer. The method will rebuild the cached computer if necessary.
* The live computer will be returned if snapshots are disabled.
*/
@VisibleForTesting(visibility = Visibility.PACKAGE)
@NonNull
public Computer snapshotComputer() {
return snapshotComputer(true /*allowLiveComputer*/);
}进一步的实现会检查线程是否持有 PMS 锁、缓存 snapshot 的版本以及待处理版本:
public Computer snapshotComputer(boolean allowLiveComputer) {
var isHoldingPackageLock = Thread.holdsLock(mLock);
if (allowLiveComputer) {
if (isHoldingPackageLock) {
// If the current thread holds mLock then it may have modified state but not
// yet invalidated the snapshot. Always give the thread the live computer.
return mLiveComputer;
}
}
var oldSnapshot = sSnapshot.get();
var pendingVersion = sSnapshotPendingVersion.get();
if (oldSnapshot != null && oldSnapshot.getVersion() == pendingVersion) {
return oldSnapshot.use();
}因此 snapshot() 不是简单的“复制一份 map”。没有持锁时通常复用版本匹配的只读 snapshot;持有 mLock 且允许 live computer 时,为了看到当前线程尚未发布到 snapshot 的修改,可能返回 live computer。这个细节解释了为什么调用方必须理解自己的锁状态,而不能只凭方法名假定永远是独立副本。
5.2 owner分层
把几个 Base 方法并排看,可以看到三种不同 owner:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
public final boolean isPermissionsReviewRequired(String packageName, int userId) {
return getPermissionManager().isPermissionsReviewRequired(packageName, userId);
}
public final Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) {
return getSuspendPackageHelper().getSuspendedPackageLauncherExtras(snapshot(), packageName,
userId, Binder.getCallingUid());
}
public final void removeDistractingPackageRestrictions(String packageName, int userId) {
getDistractingPackageHelper().removeDistractingPackageRestrictions(snapshot(),
new String[]{packageName}, userId);
}
public final List<String> getApksInApex(String apexPackageName) {
return getApexManager().getApksInApex(apexPackageName);
}isPermissionsReviewRequired() 直接问权限 owner;暂停包和 distraction 先取得 snapshot,再交给各自 helper;APEX APK 列表完全由 ApexManager 提供。PackageManagerInternal 的方法聚合了内部能力,但不拥有所有状态。调试某个结果时,要顺着方法进入实际 owner,而不是停在接口名上。
6. Intent解析
6.1 Activity查询
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@Override
@Deprecated
public final List<ResolveInfo> queryIntentActivities(
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
int filterCallingUid, int userId) {
return snapshot().queryIntentActivitiesInternal(intent, resolvedType, flags,
filterCallingUid, userId);
}调用方必须在传入前准备好 resolvedType。接口文档明确要求通过 Intent.resolveTypeIfNeeded(ContentResolver) 解析 MIME type;Base 不会自动替调用方修正一个错误的 type。结果随后由 Computer 结合 user、visibility、组件 enabled state 和 flags 过滤。
6.2 Receiver查询
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@Override
@Deprecated
public final List<ResolveInfo> queryIntentReceivers(
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
int filterCallingUid, int callingPid, int userId, boolean forSend,
String[] includedPackages) {
final List<ResolveInfo> result = getResolveIntentHelper().queryIntentReceiversInternal(
snapshot(), intent,
resolvedType, flags, userId, filterCallingUid, callingPid, forSend);
// TODO: b/428262517 - filter out packages that are not in includedPackages close to
// intent resolution.
if (includedPackages != null) {
result.removeIf(resolvedInfo -> !ArrayUtils.contains(
includedPackages, resolvedInfo.activityInfo.packageName));
}
return result;
}这条路径有两个教学价值。
其一,callingPid 不是冗余参数。解析 helper 可能用它参与启动/发送场景的策略判断;本地调用方必须传入正确 PID 或明确的无效值。其二,includedPackages 当前是在解析完成后对返回列表做二次过滤,源码 TODO 已经说明未来希望把过滤靠近 resolution 阶段。也就是说,“返回列表只含 includedPackages”在当前版本成立,但它不是最早的候选筛选点,性能和中间候选可见性不能按未来设计臆测。
6.3 LaunchParams示例
LaunchParamsPersister 使用 package list 清理已经不存在包的窗口启动参数:
源码文件:frameworks/base/services/core/java/com/android/server/wm/LaunchParamsPersister.java
void onSystemReady() {
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
mPackageList = pmi.getPackageList(new PackageListObserver());
}
private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
@Override
public void onPackageAdded(String packageName, int uid) {}
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mSupervisor.mService.getGlobalLock()) {
removeRecordForPackage(packageName);
}
}
}随后后台加载任务把快照包名复制成集合:
final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames());这里 PackageList 的初始内容和后续 remove 回调承担不同职责:初始快照用于清理磁盘上的陈旧文件,回调用于运行期间删除对应内存记录。两个阶段之间存在时间窗口,所以 observer 不能被当成“历史事件重放”;它只保证注册后的变化通知。
7. PackageList
7.1 observer接口
源码文件:frameworks/base/services/core/java/android/content/pm/PackageManagerInternal.java
/**
* Observer called whenever the list of packages changes.
*
* @deprecated please use {@link com.android.internal.content.PackageMonitor} instead.
* PackageMonitor covers more installation and uninstallation corner cases than
* PackageListObserver.
*/
@Deprecated
public interface PackageListObserver {
/** A package was added to the system. */
default void onPackageAdded(@NonNull String packageName, int uid) {}
/** A package was changed - either installed for a specific user or updated. */
default void onPackageChanged(@NonNull String packageName, int uid) {}
/** A package was removed from the system. */
default void onPackageRemoved(@NonNull String packageName, int uid) {}
}新代码应优先使用 PackageMonitor,原因不是风格偏好,而是源码注释明确指出它覆盖更多安装/卸载边角情况。本文仍分析 PackageListObserver,因为 Android 17 中多个 system service 仍依赖它,并且它能清楚展示“初始快照 + 事件通知”的契约。
7.2 初始列表
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public PackageList getPackageList(@Nullable PackageListObserver observer) {
final ArrayList<String> list = new ArrayList<>();
PackageManagerService.this.forEachPackageState(snapshot(), packageState -> {
AndroidPackage pkg = packageState.getPkg();
if (pkg != null) {
list.add(pkg.getPackageName());
}
});
final PackageList packageList = new PackageList(list, observer);
if (observer != null) {
mPackageObserverHelper.addObserver(packageList);
}
return packageList;
}初始列表来自一个 snapshot(),并且只把 packageState.getPkg() != null 的包名加入列表。状态对象存在但 APK 内容暂时不可用时,不会出现在这个 PackageList 中。这个筛选与 getPackageStates() 的语义不同,不能用 package name 列表推断所有持久化状态都已可加载。
7.3 所有权与close
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageList.java
/**
* All of the package name installed on the system.
* <p>A self observable list that automatically removes the listener when it goes out of scope.
*/
public class PackageList implements PackageListObserver, AutoCloseable {
private final PackageListObserver mWrappedObserver;
private final List<String> mPackageNames;
/**
* Ownership of the given {@link List} transfers to this object and should not
* be modified by the caller.
*/
public PackageList(@NonNull List<String> packageNames, @Nullable PackageListObserver observer) {
mPackageNames = packageNames;
mWrappedObserver = observer;
}
@Override
public void close() throws Exception {
LocalServices.getService(PackageManagerInternal.class).removePackageListObserver(this);
}
/**
* Returns the names of packages installed on the system.
* <p>The list is a copy-in-time and the actual set of installed packages may differ. Real
* time updates to the package list are sent via the {@link PackageListObserver} callback.
*/
public @NonNull List<String> getPackageNames() {
return mPackageNames;
}
}这里有两个生命周期规则。
- 列表所有权转移给
PackageList,调用方不应继续修改传入的 list。 getPackageNames()返回的是 copy-in-time;真正的变化靠 observer 回调,使用结束后应调用close()移除 wrapper。
close() 不会撤销已经返回的列表内容,也不会补发 close 之前错过的事件。它只从 PackageObserverHelper 的活动集合中移除当前 wrapper。
7.4 不可变快照
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageObserverHelper.java
// True set of observers, immutable, used to iterate without blocking the lock, since
// callbacks can take a long time to return.
@GuardedBy("mLock")
private ArraySet<PackageListObserver> mActiveSnapshot = new ArraySet<>();
public void addObserver(@NonNull PackageListObserver observer) {
synchronized (mLock) {
ArraySet<PackageListObserver> set = new ArraySet<>(mActiveSnapshot);
set.add(observer);
mActiveSnapshot = set;
}
}
public void notifyRemoved(@NonNull String packageName, int uid) {
ArraySet<PackageListObserver> observers;
synchronized (mLock) {
observers = mActiveSnapshot;
}
final int size = observers.size();
for (int index = 0; index < size; index++) {
observers.valueAt(index).onPackageRemoved(packageName, uid);
}
}写入集合时复制,通知时只在锁内取得引用,真正执行回调在锁外进行。这样 callback 可以持有自己的锁、访问文件或执行较慢逻辑,而不会阻塞 observer 注册/移除。但它也意味着:通知开始后,刚刚 close() 的 observer 可能仍在本轮不可变集合中并收到一次回调;调用方必须让回调具备幂等性,并在自身状态锁内判断是否仍有效。
PMS 在包生命周期事件中转发通知:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public void notifyPackageAdded(String packageName, int uid) {
mPackageObserverHelper.notifyAdded(packageName, uid);
}
@Override
public void notifyPackageChanged(String packageName, int uid) {
mPackageObserverHelper.notifyChanged(packageName, uid);
}
@Override
public void notifyPackageRemoved(String packageName, int uid) {
mPackageObserverHelper.notifyRemoved(packageName, uid);
UserPackage.removeFromCache(UserHandle.getUserId(uid), packageName);
}删除事件除了通知 observer,还清理 UserPackage 缓存。observer 收到回调并不等于所有 PMS 派生缓存都已经在它返回前完成重建;这里能确定的生效顺序是“helper 回调完成后执行 UserPackage cache remove”,其他服务自己的异步工作仍由各自 owner 决定。
8. 状态mutation
8.1 读改提交
源码文件:frameworks/base/services/core/java/android/content/pm/PackageManagerInternal.java
/**
* Records a state which can be used to detect if package state has changed between a read and
* commit. The caller should:
* <ol>
* <li>Call this method before reading any state.</li>
* <li>Read state and calculate the desired mutation without holding the PackageManager lock.</li>
* <li>Call commitPackageStateMutation() with the returned state and a lean consumer.</li>
* </ol>
*/
@NonNull
public abstract PackageStateMutator.InitialState recordInitialState();提交方法的文档还说明:读和提交之间可能发生包安装/更新或其他 state mutation;如果结果不是成功,调用方可以在持有真实写锁的情况下从头重试。这个 API 的设计目标是把昂贵的计算移出 PMS 锁,同时仍能发现读到的世界已经过期。
8.2 双序列
源码文件:frameworks/base/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
private static final AtomicLong sStateChangeSequence = new AtomicLong();
public static void onPackageStateChanged() {
sStateChangeSequence.incrementAndGet();
}
@NonNull
public InitialState initialState(int changedPackagesSequenceNumber) {
return new InitialState(changedPackagesSequenceNumber, sStateChangeSequence.get());
}InitialState 保存 package sequence 和全局 state sequence。前者由 ChangedPackagesTracker 提供,表示包集合/版本层面的变化;后者由 PackageStateMutator.onPackageStateChanged() 递增,表示包状态字段发生变化。两条序列分开,提交结果才能区分“包列表变了”“状态变了”以及“两者都变了”。
8.3 写锁提交
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@NonNull
public PackageStateMutator.Result commitPackageStateMutation(
@Nullable PackageStateMutator.InitialState initialState,
@NonNull Consumer<PackageStateMutator> consumer) {
synchronized (mPackageStateWriteLock) {
final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
initialState, mChangedPackagesTracker.getSequenceNumber());
if (result != PackageStateMutator.Result.SUCCESS) {
return result;
}
consumer.accept(mPackageStateMutator);
mPackageStateMutator.onFinished();
}
return PackageStateMutator.Result.SUCCESS;
}生成结果不是异常,而是控制流信号。冲突时 consumer 根本不会运行;调用方必须检查 isCommitted()/isPackagesChanged()/isStateChanged(),决定重试、放弃或在自己的锁中重新计算。consumer 被限制为“只改变 state values 的精简逻辑”,不能在其中塞入安装、磁盘 I/O 或跨服务长耗时操作。
8.4 Result取值
源码文件:frameworks/base/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
public static final Result SUCCESS = new Result(true, false, false, false);
public static final Result PACKAGES_CHANGED = new Result(false, true, false, false);
public static final Result STATE_CHANGED = new Result(false, false, true, false);
public static final Result PACKAGES_AND_STATE_CHANGED = new Result(false, true, true, false);
public static final Result SPECIFIC_PACKAGE_NULL = new Result(false, false, true, true);
public boolean isCommitted() {
return mCommitted;
}
public boolean isPackagesChanged() {
return mPackagesChanged;
}
public boolean isStateChanged() {
return mStateChanged;
}
public boolean isSpecificPackageNull() {
return mSpecificPackageNull;
}不要把这些结果写成 enum,也不要把 STATE_CHANGED 当成“写入成功”。它表示初始读取后 state sequence 发生过变化,提交没有执行。只有 SUCCESS.isCommitted() 为真时,consumer 才在写锁内运行。
8.5 单包提交
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@NonNull
public PackageStateMutator.Result commitPackageStateMutation(
@Nullable PackageStateMutator.InitialState initialState, @NonNull String packageName,
@NonNull Consumer<PackageStateWrite> consumer) {
PackageStateMutator.Result result = null;
if (Thread.holdsLock(mPackageStateWriteLock)) {
// If the thread is already holding the lock, this is likely a retry based on a prior
// failure, and re-calculating whether a state change occurred can be skipped.
result = PackageStateMutator.Result.SUCCESS;
}
synchronized (mPackageStateWriteLock) {
if (result == null) {
result = mPackageStateMutator.generateResult(
initialState, mChangedPackagesTracker.getSequenceNumber());
}
if (result != PackageStateMutator.Result.SUCCESS) {
return result;
}
PackageStateWrite state = mPackageStateMutator.forPackage(packageName);
if (state == null) {
return PackageStateMutator.Result.SPECIFIC_PACKAGE_NULL;
} else {
consumer.accept(state);
}
state.onChanged();
}
return PackageStateMutator.Result.SUCCESS;
}这个重载还表达了两个并发判断:如果调用线程已经持有写锁,可能是失败后的重试,不再重复生成冲突结果;否则第一次提交会重新检查序列。包不存在时返回 SPECIFIC_PACKAGE_NULL,不会调用 consumer。
在 Android 17 的 PackageStateMutator 中,forPackage() 返回一个可空状态的 wrapper,并由 setter 对 null 状态 no-op;因此阅读具体版本源码时要以 PMS 入口的 state == null 分支和 forPackageNullable() 的实现为准,不能凭 API 名称假设所有空包路径都完全相同。调用方仍应把 isSpecificPackageNull() 当作“目标包未找到”的独立结果处理。
8.6 mutation示例
源码文件:frameworks/base/services/core/java/com/android/server/pm/SuspendPackageHelper.java
mPm.commitPackageStateMutation(null, mutator -> {
final int size = changedPackagesList.size();
for (int index = 0; index < size; index++) {
final String packageName = changedPackagesList.valueAt(index);
final PackageUserStateWrite userState = mutator.forPackage(packageName)
.userState(targetUserId);
userState.setSuspended(suspended, suspendingPackage, appExtras,
launcherExtras, dialogInfo);
}
});这里传入 null InitialState,表示调用方已经在自己的流程中决定不做乐观冲突检查,直接让 PMS 在写锁内执行 mutation。Suspension 的策略校验、受影响包计算和通知准备发生在 consumer 之前;consumer 只写 per-user 状态。写入成功后,后续广播/缓存刷新仍由 SuspendPackageHelper 和 PMS 的其他路径负责,不能把 commitPackageStateMutation() 的返回成功解释成用户界面已经更新。
9. 典型消费者
9.1 PermissionPolicy
注册 observer 后,PermissionPolicyService 在包变化时按 user 重新授予默认权限:
源码文件:frameworks/base/services/core/java/com/android/server/policy/PermissionPolicyService.java
mPackageManagerInternal.getPackageList(new PackageListObserver() {
@Override
public void onPackageAdded(String packageName, int appId) {
final int[] userIds = LocalServices.getService(UserManagerInternal.class)
.getUserIds();
for (final int userId : userIds) {
if (isStarted(userId)) {
grantDefaultPermissionsToPackage(packageName, userId);
}
}
}
});在同步某个 UID 的权限和 AppOps 时,它又使用 appId 到包对象的映射:
final int appId = UserHandle.getAppId(uid);
final List<AndroidPackage> pkgs = mPackageManagerInternal.getPackagesForAppId(appId);
for (int i = 0; i < pkgs.size(); i++) {
final AndroidPackage pkg = pkgs.get(i);
synchroniser.addPackage(pkg.getPackageName());
}
synchroniser.syncPackages();这里返回的是 framework 内部 AndroidPackage,不是跨进程的 PackageInfo。PermissionPolicy 是消费者,不拥有 package manifest;它把包名交给自己的 PermissionToOpSynchroniser,后者才执行同步。
9.2 区域检测
源码文件:frameworks/base/services/core/java/com/android/server/display/SmallAreaDetectionController.java
SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
mContext = context;
mPackageManager = LocalServices.getService(PackageManagerInternal.class);
deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
BackgroundThread.getExecutor(),
new SmallAreaDetectionController.OnPropertiesChangedListener());
mPackageManager.getPackageList(new PackageReceiver());
}
private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
@Override
public void onPackageAdded(@NonNull String packageName, int uid) {
float threshold = 0.0f;
synchronized (mLock) {
if (mAllowPkgMap.containsKey(packageName)) {
threshold = mAllowPkgMap.get(packageName);
}
}
if (threshold > 0.0f) {
setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold);
}
}
}它把 PMS 的 package-added 事件与自己的 allowlist 和硬件阈值绑定起来。PackageManagerInternal 只负责告诉它 package 生命周期;allowlist owner 是 SmallAreaDetectionController,阈值生效由 setSmallAreaDetectionThreshold() 决定。由于回调可能在 PMS 锁外执行,消费者必须自己保护 mAllowPkgMap,源码中的 synchronized (mLock) 正是这个边界。
9.3 LaunchParams清理
前面的代码已经展示了它如何注册 observer。后台任务还会验证文件名和包名:
源码文件:frameworks/base/services/core/java/com/android/server/wm/LaunchParamsPersister.java
final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames());
final File[] paramsFiles = launchParamsFolder.listFiles();
final ArrayMap<ComponentName, PersistableLaunchParams> map =
new ArrayMap<>(paramsFiles.length);
for (File paramsFile : paramsFiles) {
if (!paramsFile.isFile()) {
Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file.");
continue;
}
if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) {
Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName());
filesToDelete.add(paramsFile);
continue;
}如果 package 在初始快照后被卸载,remove callback 会清理内存记录;如果某个文件在读取期间损坏或命名异常,Persister 自己加入删除队列。两个失败路径的 owner 不同:package 生命周期由 PMS 通知,文件一致性由 Persister 处理。
10. 失败与边界
10.1 空值与异常
PackageManagerInternal 的方法返回值并不统一。几个真实例子:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
public final long getCeDataInode(String packageName, int userId) {
final PackageStateInternal packageState =
snapshot().getPackageStateInternal(packageName);
if (packageState == null) {
return 0;
} else {
return packageState.getUserStateOrDefault(userId).getCeDataInode();
}
}
public final boolean wasPackageEverLaunched(String packageName, int userId) {
final PackageStateInternal packageState = getPackageStateInternal(packageName);
if (packageState == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
return !packageState.getUserStateOrDefault(userId).isNotLaunched();
}getCeDataInode() 找不到包返回 0,而 wasPackageEverLaunched() 找不到包抛出 IllegalArgumentException。这不是实现风格差异,而是两个调用契约的不同:inode 查询把 0 作为“错误/不存在”的哨兵,启动历史查询要求调用方先确认 package identity。内部 API 使用者必须阅读具体方法的 Javadoc 和实现,不能统一 catch 或统一把空值当正常状态。
10.2 锁与snapshot
flushPackageRestrictions() 明确在 PMS lock 下执行持久化:
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public void flushPackageRestrictions(int userId) {
synchronized (mLock) {
PackageManagerService.this.flushPackageRestrictionsAsUserInternalLocked(userId);
}
}接口给它标注了 @WorkerThread,说明调用方不能把它放到主线程。它是“立即写盘”的 API,返回只表示方法完成了 PMS 侧 flush;文件系统错误、后续 observer 处理和其他服务缓存并不由这个返回值承诺。
10.3 状态与通知
状态 mutation 的 onFinished() 只标记被改变的 PackageSetting;包添加/删除 observer 由安装卸载流程显式调用 notifyPackageAdded/Changed/Removed()。两条通知线不要混为一谈:
- 修改 per-user suspension、stopped、label 等状态,通常走
PackageStateMutator,影响 snapshot 版本或 watchable 状态。 - 包真正加入、更新或从系统移除,才触发
PackageListObserver的 package 生命周期回调。
因此一个“包状态 mutation 成功”不自动意味着 onPackageChanged() 会回调;是否通知取决于实际 PMS 流程是否发生了 package-level 变化。
11. 测试证据
11.1 测试夹具
测试文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceInternalTest.java
@Before
public void setUp() throws Exception {
mCloseable = MockitoAnnotations.openMocks(this);
PackageManagerServiceInjector injector = rule.mocks().getInjector();
rule.system().stageNominalSystemState();
PackageUserStateImpl packageUserStateImpl = new PackageUserStateImpl();
when(mSnapshotComputer.getPackageStateForInstalledAndFiltered(
eq(TEST_PACKAGE_NAME), anyInt(), anyInt()))
.thenReturn(mPackageStateInternal);
when(mPackageStateInternal.getUserStateOrDefault(eq(TEST_USER_ID)))
.thenReturn(packageUserStateImpl);
PackageManagerService packageManagerService =
spy(new PackageManagerService(injector, mTestParams));
doReturn(mSnapshotComputer).when(packageManagerService).snapshotComputer();
mPackageManagerInternal = packageManagerService.new PackageManagerInternalImpl();
}这个夹具把 snapshotComputer() 替换成 mock,并提供一个可写的 PackageUserStateImpl。因此它适合证明 InternalImpl 到 helper/state 的 Java 委托和异常契约,不适合证明真实启动顺序、真实 snapshot 版本发布、Binder calling UID 或磁盘 XML 持久化。
11.2 三个边界
源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceInternalTest.java
@Test
public void testGetPersonalContextMode_defaultUnset() {
assertThat(
mPackageManagerInternal.getPersonalContextMode(
TEST_PACKAGE_NAME, TEST_PROCESS_ID, TEST_USER_ID))
.isEqualTo(PackageManager.PERSONAL_CONTEXT_MODE_UNSET);
}
@Test
public void testSetPersonalContextMode_succeeds() {
int expectedValue = PackageManager.PERSONAL_CONTEXT_MODE_USER_OFF;
boolean setResult = mPackageManagerInternal.setPersonalContextMode(
TEST_PACKAGE_NAME, TEST_PROCESS_ID, TEST_USER_ID, expectedValue);
assertThat(mPackageManagerInternal.getPersonalContextMode(
TEST_PACKAGE_NAME, TEST_PROCESS_ID, TEST_USER_ID)).isEqualTo(expectedValue);
assertThat(setResult).isTrue();
}
@Test
public void testSetPersonalContextMode_valueUnchanged_succeeds() {
int expectedValue = PackageManager.PERSONAL_CONTEXT_MODE_USER_OFF;
mPackageManagerInternal.setPersonalContextMode(
TEST_PACKAGE_NAME, TEST_PROCESS_ID, TEST_USER_ID, expectedValue);
boolean secondSetResult = mPackageManagerInternal.setPersonalContextMode(
TEST_PACKAGE_NAME, TEST_PROCESS_ID, TEST_USER_ID, expectedValue);
assertThat(secondSetResult).isFalse();
}测试输入是固定包名、进程号、userId 和 mode。动作分别是读取默认值、写入后读取、重复写入同一值。断言证明三件事:未设置时返回 UNSET;首次写入可被后续读取观察到并返回 true;幂等写入返回 false 而不抛异常。
它没有证明跨用户策略、并发 mutation、持久化到 packages.xml 或真实 system_server 启动顺序。要证明这些边界,需要整合测试或针对 PackageStateMutator/Settings 的独立测试,不能把这三个单元测试的通过范围扩大。
11.3 失败输入的断言
@Test
public void testSetPersonalContextMode_invalidPackageName_throws() {
ParcelableException parcelableException = assertThrows(
ParcelableException.class,
() -> mPackageManagerInternal.setPersonalContextMode(
"invalid.package", TEST_PROCESS_ID, TEST_USER_ID,
PackageManager.PERSONAL_CONTEXT_MODE_USER_OFF));
assertThat(parcelableException.getCause())
.isInstanceOf(PackageManager.NameNotFoundException.class);
}这里的输入是不存在的 package,动作是调用 setter,断言是外层 ParcelableException 的 cause 为 NameNotFoundException。它证明了该 API 为内部异常使用了可传递的包装形式;它不证明所有 PackageManagerInternal 方法都会统一包装 NameNotFoundException,因为前面 wasPackageEverLaunched() 就直接抛 IllegalArgumentException。
12. 阅读路线
当你在 PackageManagerInternal 看到一个新方法,不要先猜它属于“查询”还是“写入”。按下面的顺序追:
- 先看参数语义。 是否有
filterCallingUid、callingUid、callingPid、userId?没有的话,检查实现是否固定使用SYSTEM_UID或Process.myUid()。 - 再看 Base 是否 final 代理。 如果实现只是
snapshot().foo(...),重点转到Computer的过滤和 owner;如果调用 helper/PMS,继续看锁、异步和持久化。 - 确定状态 owner。 权限看
PermissionManagerServiceInternal,Intent 看ResolveIntentHelper,APEX 看ApexManager,包状态写入看PackageStateMutator和PackageSetting。 - 确认 snapshot 边界。 多步读取是否共用一个
Computer?如果每个 PMI 方法各取一次,更新竞争时可能看到不同版本。 - 区分 package 事件和 state mutation。 observer 回调来自
notifyPackage*(),不是所有 state setter 都会触发。 - 追失败和清理。 找不到包是返回空、哨兵值还是异常?返回后是否还有 handler、磁盘写入、广播或 cache invalidation?
- 最后读测试。 把测试的输入、动作、断言和未覆盖范围写出来,避免把 mock 委托测试当作真实启动或并发证明。
一个紧凑的调用图如下:
小结
Android 17 的 PackageManagerInternal 可以概括为一条进程内的“契约入口”,但它不是一个无状态的万能门面:
LocalServices让同进程服务取得PackageManagerInternalImpl,注册时机参与 PMS 启动依赖图。PackageManagerInternalBase为简单查询自动取得 snapshot;多步业务若要一致视图,必须显式复用同一个Computer。- 本地调用没有 Binder 自动 calling UID,过滤 UID、PID 和 userId 必须由调用方正确传入;固定
SYSTEM_UID只适用于源码明确约定的可信方法。 PackageList是时间点列表加 observer,不是实时可变集合;observer 有独立锁和 close 生命周期,回调发生在 helper 锁外。PackageStateMutator通过两条序列和写锁执行乐观校验;冲突结果意味着 consumer 没有运行,成功也只代表 PMS 状态写入完成。- 权限、解析、APEX、暂停包和持久化各有 owner;调试结果必须继续追到真正的 helper、snapshot 或 Settings,而不能停在接口层。
如果要继续读 PMS 内部 API,下一步应把 PackageManagerLocal 的显式 snapshot 生命周期与本文的便利代理并排阅读:前者展示 Android 正在迁移的目标形态,后者则解释为什么 Android 17 仍需要这套兼容层。
