8. ServiceManager 的职责:服务的注册、查询与权限管理

ServiceManager,简称 SM,是 Binder 通信的“大管家”。

我个人习惯把它比作一个电话总机。你想联系某个服务,先打给总机,总机告诉你号码,你再拨过去。没有它,所有服务都得互相知道对方的地址,那系统就乱套了。

8.1 ServiceManager 是什么?

它是一个特殊的 Binder 服务,句柄永远是 0。任何进程想注册或查询服务,都得先跟它打交道。

说白了,它就是一张全局的“服务名 → Binder 引用”映射表。这张表存在 SM 自己的进程里,所有客户端和服务端都信任它。

核心职责就三条:

  • 服务注册:服务端进程把自己的 Binder 实体告诉 SM。
  • 服务查询:客户端进程通过服务名,从 SM 拿到 Binder 引用。
  • 权限管理:SM 会检查谁有资格注册或查询某些服务。

嗯,这里要注意:SM 本身也是一个 Binder 服务,但它启动得最早。Android 系统一开机,init 进程就把 SM 拉起来了,然后其他服务才陆续注册进来。

8.2 服务注册流程

服务端想提供服务,第一步就是调用 ServiceManager.addService()。我带你看看它背后发生了什么。

// 服务端代码示例
// 假设我们有一个名为 "my_service" 的服务
IBinder binder = new MyBinder();
ServiceManager.addService("my_service", binder);

这行代码一执行,Binder 驱动会做几件事:

  1. 服务端进程把自己的 Binder 实体打包成一个特殊的数据包。
  2. 数据包的目标句柄是 0,也就是 SM。
  3. Binder 驱动把这个数据包转发给 SM 进程。
  4. SM 收到后,把服务名和 Binder 引用存到自己的红黑树里。

我在项目中遇到过一个问题:某个服务注册时,SM 返回了“权限不足”的错误。当时排查了半天,发现是服务进程的 UID 不在 SM 的白名单里。嗯,这个后面会细说。

8.3 服务查询流程

客户端想用某个服务,调用 ServiceManager.getService()。流程跟注册类似,但方向相反。

// 客户端代码示例
IBinder binder = ServiceManager.getService("my_service");
if (binder != null) {
    // 拿到 binder 后,就可以调用服务端的方法了
    IMyService service = IMyService.Stub.asInterface(binder);
    service.doSomething();
}

查询流程的关键点:

  • 客户端发送查询请求到 SM(句柄 0)。
  • SM 在自己的映射表里查找服务名。
  • 如果找到,SM 把对应的 Binder 引用返回给客户端。
  • 如果没找到,返回 null。客户端需要自己处理这种情况。

避坑指南:我曾经遇到过一个 bug,客户端频繁调用 getService() 查询同一个服务,导致 SM 负载过高。后来我建议客户端在第一次查询成功后,把 Binder 引用缓存起来,不要每次都去查 SM。这个优化立竿见影。

8.4 权限管理

SM 不是谁都能随便注册服务的。它有一套权限检查机制。

你想想看,如果任何 App 都能注册一个叫 "activity" 的服务,那系统不就乱套了?所以 SM 会检查调用者的 UID 和 PID。

操作 权限要求 说明
addService 通常需要系统权限 普通 App 不能注册系统级服务
getService 一般无限制 任何进程都可以查询服务
checkService 一般无限制 跟 getService 类似,但返回 null 而不是抛异常

为什么会这样?因为服务注册是“写”操作,影响全局;服务查询是“读”操作,风险小得多。

我记得有一次,一个第三方 App 想注册一个自定义服务,结果被 SM 拒绝了。后来我们通过 SELinux 策略给它开了白名单,才解决问题。

注意:SM 的权限检查是粗粒度的。它只检查调用者的 UID 是否在允许列表中,不检查具体的行为。更细粒度的权限控制,需要服务端自己实现。

8.5 整体架构图

下面这张图展示了 ServiceManager 在整个 Binder 通信中的位置和作用。

ServiceManager 架构图 服务端进程 addService("name", binder) 客户端进程 getService("name") ServiceManager 句柄 = 0 服务名 → Binder 引用 注册服务 查询服务 返回 Binder 引用 权限检查:UID / PID

从图中可以看到,服务端和客户端都只跟 SM 直接通信。SM 负责“牵线搭桥”,但真正的数据传输,是客户端和服务端之间直接通过 Binder 驱动完成的。SM 不参与实际的数据交换,它只做“介绍人”。

8.6 一些实战经验

最后,分享几个我在项目中踩过的坑:

  • 服务名冲突:两个不同的服务用了同一个名字,后注册的会覆盖先注册的。我建议服务名用包名+类名的格式,比如 "com.example.myapp.IMyService"。
  • SM 重启:SM 如果挂了,所有服务都得重新注册。好在 Android 系统会监控 SM 的状态,一旦发现它挂了,会立即重启。
  • 查询超时:getService() 是同步调用,如果 SM 响应慢,客户端会卡住。我建议在 UI 线程不要直接调用,或者设置超时机制。

总结一下:ServiceManager 是 Binder 通信的基石。它负责服务的注册、查询和权限管理。理解它的工作原理,能帮你更好地排查 Binder 相关的问题。

专注资料整理