17、AMS 与 PMS 的交互:权限检查、组件查询、安装/卸载通知

这一章,我们来聊聊 AMS 和 PMS 这对老搭档。

在 Android 系统里,AMS 管的是“活着的”东西——进程、Activity、Service 的生命周期。PMS 管的是“装着的”东西——APK 的安装、卸载、权限声明。但这两者不是孤立的。你想想看,一个 App 要启动 Activity,AMS 得先问问 PMS:“这哥们儿有权限吗?它的 Activity 在清单里注册了吗?”

说白了,AMS 是前台的总指挥,PMS 是后台的档案库。它们之间的交互,贯穿了整个 Android 运行时的核心流程。

17.1 权限检查:AMS 的“门卫”机制

权限检查是 AMS 和 PMS 交互最频繁的场景之一。每次一个进程想要访问系统资源(比如定位、相机),或者启动另一个 App 的组件,AMS 都会触发一次权限检查。

这个检查的入口在 ActivityManagerService.checkPermission() 方法里。我带你看看它的调用链:

// AMS 中的权限检查入口
public int checkPermission(String permission, int pid, int uid) {
    // 1. 先查调用者是不是系统进程
    if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
        return PackageManager.PERMISSION_GRANTED;
    }
    
    // 2. 交给 PMS 去查
    return mContext.checkPermission(permission, pid, uid);
}

嗯,这里要注意。AMS 自己并不存权限数据。它只是把 pid 和 uid 传过去,真正干活的是 PMS。PMS 会去查 packages.xml 里这个 uid 对应的包名,然后看它有没有声明这个权限。

核心流程:

  • AMS 收到 Binder 调用,发起权限检查
  • AMS 通过 Context.checkPermission() 委托给 PMS
  • PMS 查询内部数据库,返回 PERMISSION_GRANTED 或 PERMISSION_DENIED
  • AMS 根据结果决定是否阻断操作

我在项目中遇到过一个问题:某个第三方 App 在 Android 11 上突然无法访问相册了。查了半天,发现是权限检查时,AMS 传过去的 uid 对应的包名在 PMS 里找不到。原因是 App 被卸载重装过,但有个僵尸进程还持有旧的 uid。嗯,这种边界情况,光看代码很难想到。

17.2 组件查询:AMS 的“导航”依赖

当 AMS 要启动一个 Activity 或者 Service 时,它得知道这个组件在哪个进程里、入口是什么。这些信息,AMS 自己是不存的。它得问 PMS。

以启动 Activity 为例,AMS 会调用 resolveIntent() 方法:

// AMS 中启动 Activity 时的组件查询
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage("com.example.app");

// 交给 PMS 解析
ResolveInfo info = mContext.getPackageManager()
    .resolveActivity(intent, PackageManager.MATCH_ALL);

// 如果找不到,直接抛异常
if (info == null) {
    throw new ActivityNotFoundException("No Activity found for " + intent);
}

这个 resolveActivity() 最终会走到 PMS 的 queryIntentActivities() 方法里。PMS 会遍历所有已安装应用的 AndroidManifest.xml,匹配 Intent-Filter,然后返回最合适的那个组件。

避坑指南:

我曾经在优化冷启动速度时发现,AMS 每次启动 Activity 都会走一遍组件查询。如果 PMS 的查询缓存没命中,就会触发一次全量扫描。对于安装了几百个 App 的设备,这个开销不小。我的建议是:在 PMS 层加一层 LRU 缓存,按 Intent 的 action + package 做 key,能省不少时间。

17.3 安装/卸载通知:AMS 的“状态同步”

App 安装或卸载时,PMS 会通知 AMS 做状态同步。这个机制是通过 IPackageManagerObserver 回调实现的。

流程大概是这样的:

  1. PMS 完成安装/卸载操作
  2. PMS 调用 AMS.packageInstalled()AMS.packageRemoved()
  3. AMS 更新内部状态,比如清除该 App 的进程缓存、杀掉残留进程
  4. AMS 广播 ACTION_PACKAGE_ADDEDACTION_PACKAGE_REMOVED

这里有个关键点:AMS 在收到卸载通知时,会主动杀掉该 App 的所有进程。为什么?因为 App 已经被卸载了,它的代码和数据都没了,进程留着只会产生空指针异常。

// AMS 处理包移除
private void handlePackageRemoved(String packageName, int uid) {
    // 1. 杀掉该包的所有进程
    killApplicationProcess(packageName, uid);
    
    // 2. 清除权限缓存
    clearPermissionCache(packageName);
    
    // 3. 通知其他系统服务
    notifyPackageRemoved(packageName, uid);
}

注意:

AMS 和 PMS 的交互是跨进程的。PMS 运行在 system_server 进程里,AMS 也在同一个进程。所以它们之间的调用其实是进程内调用,不走 Binder。但对外暴露的接口(比如 checkPermission)是 Binder 调用,会有性能开销。我在做性能分析时发现,频繁的权限检查会导致 Binder 线程池拥堵。解决方案是:对于高频权限(比如定位),在 AMS 层做一层 uid 级别的缓存。

17.4 整体交互流程

我把 AMS 和 PMS 的交互画成了一张图,方便你理解:

AMS ActivityManagerService PMS PackageManagerService checkPermission() resolveIntent() packageInstalled/Removed packages.xml 权限声明 + 组件信息 进程管理 杀掉残留进程 AMS 与 PMS 交互核心流程 交互类型 方向 触发场景 权限检查 AMS → PMS 访问系统资源、跨进程通信 组件查询 AMS → PMS 启动 Activity/Service 安装/卸载通知 PMS → AMS App 安装、卸载、更新

17.5 总结

AMS 和 PMS 的交互,说白了就是“前台指挥”和“后台档案”的协作。权限检查、组件查询、安装卸载通知,这三件事覆盖了 90% 以上的交互场景。

我个人习惯在分析系统问题时,先看是 AMS 的问题还是 PMS 的问题。如果是权限相关的,大概率是 PMS 的数据没更新;如果是进程启动相关的,大概率是 AMS 的调度逻辑出了问题。把这两个服务拆开来看,问题会清晰很多。

嗯,这一章就到这里。记住一句话:AMS 问“能不能做”,PMS 答“有没有”。


公众号:蓝海资料掘金营,微信deep3321