Skip to content

PackageManagerInternal

以 Android 17 源码为线索,拆解 PackageManagerInternal 的进程内契约、snapshot 查询、身份语境、状态写入和 observer 生命周期。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageManagerInternalLocalServicesComputer源码阅读

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

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

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

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

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

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

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

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”。下面的代码会取两次:

java
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

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

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

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

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

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

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 的版本以及待处理版本:

java
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

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

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

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

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);
        }
    }
}

随后后台加载任务把快照包名复制成集合:

java
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

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

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

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;
    }
}

这里有两个生命周期规则。

  1. 列表所有权转移给 PackageList,调用方不应继续修改传入的 list。
  2. getPackageNames() 返回的是 copy-in-time;真正的变化靠 observer 回调,使用结束后应调用 close() 移除 wrapper。

close() 不会撤销已经返回的列表内容,也不会补发 close 之前错过的事件。它只从 PackageObserverHelper 的活动集合中移除当前 wrapper。

7.4 不可变快照 ​

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

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

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

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

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

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

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

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

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

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 到包对象的映射:

java
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

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

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

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

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

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

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 失败输入的断言 ​

java
@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 看到一个新方法,不要先猜它属于“查询”还是“写入”。按下面的顺序追:

  1. 先看参数语义。 是否有 filterCallingUid、callingUid、callingPid、userId?没有的话,检查实现是否固定使用 SYSTEM_UID 或 Process.myUid()。
  2. 再看 Base 是否 final 代理。 如果实现只是 snapshot().foo(...),重点转到 Computer 的过滤和 owner;如果调用 helper/PMS,继续看锁、异步和持久化。
  3. 确定状态 owner。 权限看 PermissionManagerServiceInternal,Intent 看 ResolveIntentHelper,APEX 看 ApexManager,包状态写入看 PackageStateMutator 和 PackageSetting。
  4. 确认 snapshot 边界。 多步读取是否共用一个 Computer?如果每个 PMI 方法各取一次,更新竞争时可能看到不同版本。
  5. 区分 package 事件和 state mutation。 observer 回调来自 notifyPackage*(),不是所有 state setter 都会触发。
  6. 追失败和清理。 找不到包是返回空、哨兵值还是异常?返回后是否还有 handler、磁盘写入、广播或 cache invalidation?
  7. 最后读测试。 把测试的输入、动作、断言和未覆盖范围写出来,避免把 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 仍需要这套兼容层。