10. 服务获取流程:Client 端如何从 ServiceManager 获取服务代理?
好,我们接着聊。上一章我们讲了服务怎么注册到 ServiceManager,那这一章自然就是客户端怎么把它拿回来。
说白了,客户端要使用某个服务,它不可能直接 new 一个对象出来。Android 的服务都是跑在独立进程里的,Client 只能拿到一个代理对象,通过这个代理去跟服务端通信。那这个代理从哪来?嗯,就是从 ServiceManager 那里取。
10.1 核心入口:getService() 与 checkService()
客户端获取服务代理,最常用的两个接口是 getService() 和 checkService()。它们都在 ServiceManagerNative 这个类里封装好了。
我直接给你看关键代码,这样更直观:
// frameworks/base/core/java/android/os/ServiceManagerNative.java
public static IBinder getService(String name) throws RemoteException {
return getIServiceManager().getService(name);
}
public static IBinder checkService(String name) throws RemoteException {
return getIServiceManager().checkService(name);
}
这两个方法的区别,我简单说一下:
- getService():如果服务不存在,它会阻塞等待,直到服务注册上来才返回。说白了就是「死等」。
- checkService():如果服务不存在,直接返回 null,不等待。适合那些「有就用,没有拉倒」的场景。
10.2 获取 IServiceManager 代理
你可能会问:getIServiceManager() 又是怎么来的?
嗯,这里有个关键点。ServiceManager 本身也是一个 Binder 服务,它的句柄是固定的——0。Binder 驱动在初始化时,会把 ServiceManager 注册为 context manager,句柄永远为 0。
所以 getIServiceManager() 做的事情,就是通过 Binder 驱动拿到句柄 0 的代理,然后封装成 IServiceManager 接口:
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
// 通过 BinderInternal.getContextObject() 拿到句柄 0 的 BinderProxy
sServiceManager = ServiceManagerNative.asInterface(
BinderInternal.getContextObject());
return sServiceManager;
}
这里 BinderInternal.getContextObject() 是一个 native 方法,它最终会调用 ioctl() 向 Binder 驱动发送 BINDER_SET_CONTEXT_MGR 命令,拿到 ServiceManager 的代理。
10.3 完整调用链路
从 Client 调用 getService() 到真正拿到服务代理,中间经历了哪些步骤?我画了一张图,你一看就明白:
整个流程其实就四步:
- Client 调用 getService(),传入服务名称。
- ServiceManagerProxy 发起 Binder 事务,事务码是
GET_SERVICE_TRANSACTION。 - Binder 驱动将请求转发给 ServiceManager 服务端,服务端在自己的
svclist链表里查找服务名。 - ServiceManager 返回服务的 Binder 引用,Client 拿到这个引用后,封装成具体的服务代理接口。
10.4 返回的是什么?BinderProxy 还是 Binder 对象?
这里有个细节,很多人会搞混。ServiceManager 返回的其实是一个 flat_binder_object,里面包含的是服务端 Binder 实体的引用。
在 Binder 驱动里,这个引用会被转换成 BinderProxy 对象。也就是说,Client 拿到的本质上是一个代理对象,不是真正的服务端 Binder 实体。
我举个例子你就明白了:
// 假设我们要获取 ActivityManagerService
IBinder binder = ServiceManager.getService("activity");
// 这个 binder 其实是一个 BinderProxy 对象
// 它内部持有一个 handle,指向 AMS 在 Binder 驱动中的节点
// 然后通过 asInterface() 转换成具体接口
IActivityManager am = IActivityManager.Stub.asInterface(binder);
asInterface() 做的事情很简单:如果 Binder 对象在同一个进程,直接返回服务端实体;如果跨进程,就返回一个 Proxy 包装类。这个我们在讲 Binder 架构时详细说过。
10.5 缓存机制:ServiceManager 的优化
每次调用 getService() 都要走一遍 Binder 通信,性能开销不小。所以 Android 在 ServiceManager 类里做了缓存:
// frameworks/base/core/java/android/os/ServiceManager.java
private static final HashMap<String, IBinder> sCache = new HashMap<>();
public static IBinder getService(String name) {
IBinder service = sCache.get(name);
if (service != null) {
return service;
}
// 缓存未命中,走 Binder 通信
service = getIServiceManager().getService(name);
sCache.put(name, service);
return service;
}
这个缓存是进程级别的。同一个进程多次获取同一个服务,只有第一次会走 Binder 通信,后面直接从缓存拿。
不过要注意,这个缓存不会自动失效。如果服务端重启了,缓存里的 Binder 引用就变成死引用(dead object)了。这时候需要捕获 RemoteException,然后清掉缓存重新获取。
ServiceManager.getService() 刷新缓存。这样更健壮。
10.6 总结一下
客户端获取服务代理,本质上就是一次 Binder 通信:Client 通过句柄 0 找到 ServiceManager,告诉它「我要 xxx 服务」,ServiceManager 从自己的服务列表里找到对应的 Binder 引用,返回给 Client。
整个过程涉及三个角色:
| 角色 | 作用 | 关键点 |
|---|---|---|
| Client | 发起服务获取请求 | 通过 getService() / checkService() 调用 |
| ServiceManager | 维护服务注册表,返回服务引用 | 句柄固定为 0,svclist 链表存储服务 |
| Binder 驱动 | 转发事务,管理 Binder 节点和引用 | 负责将 flat_binder_object 转换成 BinderProxy |
嗯,这一章的内容就这些。核心就是记住:ServiceManager 是服务的中介,Client 通过它拿到代理,然后才能跟真正的服务端通信。下一章我们会深入 Binder 驱动,看看它到底是怎么管理这些 Binder 节点和引用的。
公众号:蓝海资料掘金营,微信deep3321