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 回调实现的。
流程大概是这样的:
- PMS 完成安装/卸载操作
- PMS 调用
AMS.packageInstalled()或AMS.packageRemoved() - AMS 更新内部状态,比如清除该 App 的进程缓存、杀掉残留进程
- AMS 广播
ACTION_PACKAGE_ADDED或ACTION_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 的交互画成了一张图,方便你理解:
17.5 总结
AMS 和 PMS 的交互,说白了就是“前台指挥”和“后台档案”的协作。权限检查、组件查询、安装卸载通知,这三件事覆盖了 90% 以上的交互场景。
我个人习惯在分析系统问题时,先看是 AMS 的问题还是 PMS 的问题。如果是权限相关的,大概率是 PMS 的数据没更新;如果是进程启动相关的,大概率是 AMS 的调度逻辑出了问题。把这两个服务拆开来看,问题会清晰很多。
嗯,这一章就到这里。记住一句话:AMS 问“能不能做”,PMS 答“有没有”。