ServiceManager:服务如何注册、查找与缓存
ServiceManager:服务如何注册、查找与缓存
上一章我们知道了事务怎么装、怎么送。这一章回答:客户端要调用一个服务时,怎么知道 “它在哪里、拿什么句柄”?
客户端知道服务名,并不等于已经持有远端 Binder 对象。ServiceManager 解决的是首次发现: 服务端注册“名称—Binder 引用”,客户端按名称查询并在进程内缓存接口。之后的业务事务直接 发往目标 Binder 节点。
ServiceManager 只参与服务发现
| 常见误解 | 正确理解 |
|---|---|
| 每次调用系统服务都要经过 ServiceManager | 只有“第一次找服务”经过它;找到句柄后后续调用直达服务进程 |
| ServiceManager 就是服务本身 | 它是名字服务(name → handle 的目录),业务实现都在各服务进程 |
getService() 一定能立刻拿到服务 | 服务刚启动或未注册时可能返回空,需要等待或监听 |
ServiceManager 是 Binder 的名字服务,也是驱动中的 context manager。它维护服务名到 Binder 引用的映射,但不承载各系统服务的业务实现,也不是后续调用的固定中转站。
核心结论
- 服务端进程启动服务后调用
addService(name, binder),ServiceManager 记录“名字 → handle”。 - 客户端调用
getService(name),拿到服务句柄后,用asInterface()转成类型化接口。 - 进程侧会缓存查询结果,避免每次调用都访问 ServiceManager。
- ServiceManager 本身是 handle 0 的上下文对象,所有进程都通过它做第一次查询。
- 服务重启后 handle 可能变化,缓存的旧引用失效,需要重新
getService()。
注册与查找主链
逐步解释:
- 服务端注册:SystemServer 创建服务后,把服务名字和 Binder 对象通过
addService()交给 ServiceManager。 - 映射表:ServiceManager 维护“名字 → handle”,一个名字对应一个 Binder 节点。
- 客户端查询:
getService(name)从 ServiceManager 拿句柄;查询本身是一次 Binder 调用。 - 转成接口:
asInterface()判断句柄指向本地还是远端,远端则生成 Proxy。 - 后续调用:业务调用直接发往服务进程,ServiceManager 不再参与。
为什么“找一次”而不是“次次找”
查询一次的成本虽然不高,但每个高频调用(如窗口 relayout)都多一次进程往返完全没有必要。 Android 在进程侧按名字缓存查询结果:第一次解析后,Context.getSystemService() 等服务 入口直接复用缓存。缓存失效的主要场景是服务进程重启:驱动清理旧节点,客户端再调用会抛 DeadObjectException,此时要重新查询。
源码证据
阅读目标:确认注册与查询两条主链的入口。
正文指针:ServiceManager.java getService()/addService();Native 侧 IServiceManager.cpp 与 service_manager.c 的 do_add_service()/do_find_service()。
// ServiceManager.java(伪代码,行号以 r75 为准)
public static IBinder getService(String name) {
// 先查进程内缓存;未命中则通过 ServiceManager 代理查询
}
public static void addService(String name, IBinder service) { ... }// service_manager.c(伪代码,行号以 r75 为准)
static int do_add_service(struct binder_state* bs, const char* name, uint32_t handle, ...);
static struct svcinfo* find_svc(const char* name);这段代码证明: Java 层 getService() 是入口,真正的映射表维护在 ServiceManager 进程的 svcinfo 链表中;find_svc() 按名字查找,找不到时返回空,客户端需要等待或重试。
验证与排障
环境:任意开发设备即可;以下命令不需要 root。
adb shell service list
adb shell service check window
adb shell dumpsys activity service <服务名>service list 列出已注册的服务名;service check 检查某个名字是否存在。服务未注册时 客户端 getService() 会得到空引用或超时,常见原因是服务进程崩溃、还在启动中,或 SELinux 拒绝注册。
排查顺序:先 service list 确认名字在不在;再确认服务进程是否存活;最后看 logcat 中 的注册失败日志。
常见误区
- “每次调用都经过 ServiceManager”:只有第一次查询经过,之后直达服务进程。
- “ServiceManager 是唯一能注册服务的”:Android 还有
ServiceRegistry等服务注册方式, ServiceManager 是最基础的名字服务。 - “服务重启后句柄不变”:句柄可能变,旧引用失效,必须重取。
- “
getService()返回 null 就是 Bug”:服务未启动完成时返回空是正常状态,需要等待或监听。
延伸问题
- handle 0 的上下文对象如何被所有进程信任?
addService的权限检查(SELinux 的 service_contexts)发生在哪一层?- Framework 的
ServiceManager.addServiceListener()如何让客户端在服务注册后得到通知? - 一个进程能注册多少个服务,为什么?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
ServiceManager.java | getService() / addService() | Java 层查询与注册入口 |
IServiceManager.cpp | getService() / addService() | Native 层接口实现 |
service_manager.c | do_add_service() / find_svc() | 映射表维护与查找 |
ProcessState.cpp | getContextObject() | 获取 handle 0 的上下文对象 |
公共路径:Java 侧 frameworks/base/core/java/android/os/,Native 侧 frameworks/native/libs/binder/ 与 frameworks/native/cmds/servicemanager/。 行号以 r75 检索为准。
句柄如何变成类型化接口,见 AIDL。