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,不等待。适合那些「有就用,没有拉倒」的场景。
我的经验: 在系统启动阶段,很多服务还没注册完,这时候用 getService() 很容易导致线程卡死。我建议你优先用 checkService(),配合重试机制,这样更安全。

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 的代理。

核心要点: ServiceManager 的句柄永远是 0,这是写死在 Binder 驱动里的。所以任何进程想跟 ServiceManager 通信,直接拿句柄 0 就行,不需要走查找流程。

10.3 完整调用链路

从 Client 调用 getService() 到真正拿到服务代理,中间经历了哪些步骤?我画了一张图,你一看就明白:

Client 获取服务代理完整流程 Client 进程 1. getService("xxx") ServiceManagerProxy (句柄 0 的 BinderProxy) 2. transact(GET_SERVICE_TRANSACTION) Binder 驱动 (内核空间) 3. 查找服务注册表 ServiceManager 服务端 (svclist 链表查找) 4. 返回服务 Binder 引用 Java 层 Java 层 内核层 Native 层

整个流程其实就四步:

  1. Client 调用 getService(),传入服务名称。
  2. ServiceManagerProxy 发起 Binder 事务,事务码是 GET_SERVICE_TRANSACTION
  3. Binder 驱动将请求转发给 ServiceManager 服务端,服务端在自己的 svclist 链表里查找服务名。
  4. 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 架构时详细说过。

注意: 我曾经在项目里遇到过一个坑——在 ServiceManager 返回 Binder 引用后,直接强转成具体接口类型,结果抛 ClassCastException。原因就是忘了调用 asInterface() 做转换。记住,ServiceManager 返回的永远是 IBinder,不是具体接口。

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 的缓存。我习惯在每次 Binder 调用前加一个 try-catch,如果捕获到 DeadObjectException,就主动调用 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 节点和引用的。

一句话总结: getService() 的本质就是「Client → ServiceManager → 返回 Binder 引用」的一次 Binder 事务,耗时主要在网络通信和 svclist 查找上。

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