Binder(五):ServiceManager 怎样把服务名变成可调用的 Binder?

180 阅读10分钟

Binder(五):ServiceManager 怎样把服务名变成可调用的 Binder?

前四篇已经解释:

只要客户端持有目标 Binder 引用,
Proxy 就能编码事务,
驱动就能通过 handle 找到 node 和目标进程,
Stub 就能还原方法调用。

但这把一个关键前提藏起来了:

客户端第一次调用前,目标 handle 从哪里来?它原本既不认识服务端进程,也没有任何远端 Binder 引用。

Android 用 ServiceManager 解决“根 Binder 服务发现”问题。

服务端:name + Binder 引用 → 注册到 servicemanager
客户端:name → 查询 servicemanager → 获得 Binder 引用
后续业务调用:客户端 → 目标服务,不再经过 servicemanager

这里有一条很重要的职责边界:

ServiceManager 解决“怎样通过名字找到远端 Binder 引用”;
Binder Driver 解决“拿到引用后,事务怎样路由到目标 node”。

源码口径沿用导读第四节“系列统一基线”。主链使用注册与 get / check / wait service;Android 16 的 getService2() / checkService2() 和缓存差异在后文单独说明。

为什么系统需要一个名字表

没有 ServiceManager,客户端缺少首个引用

假设系统里有:

activity
package
power
window
display
input

如果没有统一发现机制,每个客户端都必须预先知道:

服务在哪个进程;
如何获得第一个 Binder 引用;
服务重启后从哪里重新发现新的 Binder 引用;
不同进程怎样对同一服务建立自己的 handle。

这会形成一个循环:

要调用服务,先要有 Binder 引用;
要获得 Binder 引用,又需要先调用某个服务。

ServiceManager 用一个预先约定的根端点打破这个循环。

注册与查询是两条相反的数据流

image.png

读图时必须分开两条链:

上半部分:服务注册,把 name 绑定到 Binder 引用;
下半部分:服务查询,把 Binder 引用返回给客户端;
最下面:普通业务 transact 直接去目标服务。

最容易犯的错误,就是把第三条业务调用也画成经过 ServiceManager 转发。

ServiceManager 本身也是 Binder 服务

是,但它是 Binder 上下文中的特殊根服务。

Native servicemanager 进程启动时,Android 16 的 main.cpp 会:

sp<ProcessState> ps = ProcessState::initWithDriver("/dev/binder");

sp<ServiceManager> manager =
        sp<ServiceManager>::make(...);

IPCThreadState::self()->setTheContextObject(manager);

if (!ps->becomeContextManager()) {
    LOG(FATAL) << "Could not become context manager";
}

驱动把这个本地 Binder node 登记为当前 Binder context 的 context manager node。

普通客户端不需要先知道它的动态 handle。Binder 协议为上下文管理者约定了:

handle 0

所以“先找到 ServiceManager”不再依赖另一个名字服务。

handle 0 提供根发现入口

Java ServiceManager.getIServiceManager() 主干是:

private static IServiceManager getIServiceManager() {
    if (sServiceManager != null) {
        return sServiceManager;
    }

    sServiceManager = ServiceManagerNative.asInterface(
            Binder.allowBlocking(
                    BinderInternal.getContextObject()));
    return sServiceManager;
}

拆开看:

BinderInternal.getContextObject()
    ↓ JNI / ProcessState
getStrongProxyForHandle(0)
    ↓
通用 IBinder / BinderProxy
    ↓ ServiceManagerNative.asInterface()
IServiceManager.Stub.asInterface()
    ↓
类型化 IServiceManager Proxy

BinderInternal.getContextObject() 是 Java 侧引导入口;Native ProcessState::getContextObject() 最终使用 handle 0。

因此,ServiceManager 的引导发现可以压缩成:

大家预先知道 handle 0,
再用 IServiceManager AIDL 把通用 Binder 引用包装成名字服务接口。

这里讨论的是 /dev/binder 对应的 Framework Binder context。handle 0 是这个 Binder context 的 context manager 入口,不是跨 /dev/binderhwbindervndbinder 和其他 Binder context 的全局唯一对象。

注册事务传递名字与 Binder 引用

以 system_server 中一个 Java Binder 服务为例。为使代码片段可独立核对,这里假设文章专用的最小接口只声明一个方法:

interface ICalculator {
    int add(int a, int b);
}

对应 Stub 可以直接注册:

ICalculator.Stub calculatorBinder =
        new ICalculator.Stub() {
            @Override
            public int add(int a, int b) {
                return a + b;
            }
        };

ServiceManager.addService("calculator", calculatorBinder);

注:这不是仓库 BinderLab 的 Android Service 组件 CalculatorService;前者是注册给 ServiceManager 的 Binder Stub,后者由应用组件框架通过 bindService() 管理。两种“Service”不能用同一个构造示例混写。

Java ServiceManager.addService() 调用:

getIServiceManager().addService(
        name,
        calculatorBinder,
        allowIsolated,
        dumpPriority);

注意第二个参数类型是:

IBinder

这意味着注册的不是:

Java 类名;
服务端对象地址;
服务端 PID;
一个全局 handle 整数。

而是一个 Binder 接口引用。AIDL Proxy 会通过 writeStrongBinder() / writeStrongInterface() 把服务端本地 Binder 实体作为特殊对象写入注册事务;驱动在 servicemanager 进程中把它转换成 servicemanager 自己的远端引用。

所以注册链是:

服务端本地 Binder / node
    ↓ 作为 addService 参数传递
Binder Driver
    ↓ 为 servicemanager 建立 binder_ref / handle
servicemanager 中的 sp<IBinder>(通常为 BpBinder)

ServiceManager 没有复制服务端 Java 对象。

servicemanager 保存引用与服务元数据

Native ServiceManager::addService() 先检查:

调用者是否有 add 权限;
服务名是否合法;
Binder 是否为空;
稳定性 / 声明要求是否满足;
是否能为远端 Binder 注册死亡通知。

然后把服务记录放入名字表。

概念上可以写成:

mNameToService[
    "calculator"
] = {
    binder = 对目标 node 的 IBinder 引用,
    allowIsolated,
    dumpPriority,
    注册者上下文,
    元数据...
}

这里保存的是 servicemanager 进程可以持有和再传递的 Binder 引用,不是服务端业务对象内存。

查询结果经 reply 变成客户端的新 handle

Java ServiceManager.getService(name) 先查进程内 sCache,未命中才进入:

rawGetService(name)

稳定模型是:Java 通过 IServiceManager 查询名字;servicemanager 查注册表,并把目标 IBinder 放进 reply。Android 16 当前具体调用 getService2() 并返回元数据包装,版本差异放在后面的“发现接口与缓存”中单独说明。

这个 Binder 引用作为 reply 中的特殊对象返回客户端时,驱动再次执行对象引用转换:

servicemanager 持有的目标引用
    ↓ 放入 reply Parcel
Binder Driver
    ↓ 为查询客户端建立/复用 binder_ref
客户端自己的 handle
    ↓
BpBinder → BinderProxy

所以客户端拿到的 IBinder 是自己进程中的远端代理入口,不是 servicemanager 的 handle 原样复制。

Stub.asInterface() 把通用引用包装成业务接口

ServiceManager.getService() 返回的是通用:

IBinder

业务代码还需要恢复类型化接口:

IBinder binder = ServiceManager.getService("calculator");
ICalculator calculator = ICalculator.Stub.asInterface(binder);

跨进程时:

IBinder = BinderProxy
    ↓ asInterface()
ICalculator.Stub.Proxy

同进程时 queryLocalInterface() 仍可能直接返回本地实现。

这再次说明:

ServiceManager 只负责返回通用 Binder 引用;
AIDL Stub.asInterface() 负责恢复业务接口视图。

找到服务后,业务事务直接去目标 node

查询完成后,客户端已经拥有:

自己的 BinderProxy / BpBinder / handle

后续:

calculator.add(1, 2);

直接形成:

客户端
    → 目标服务 binder_node

ServiceManager 不参与每一笔业务事务。

可以把它类比为:

ServiceManager:通讯录查询
Binder Driver:拨号与线路路由
目标服务:真正接听并处理业务

类比边界是:Binder 引用具有驱动维护的对象与生命周期语义,不只是一个普通电话号码。

Android 16 的发现接口与缓存

getService2() / checkService2() 返回了什么新信息

Android 16 的 IServiceManager.aidl 已把旧接口标记为兼容路径,并提供:

Service getService2(String name);
Service checkService2(String name);

返回值不再只有裸 IBinder,还可以表达:

serviceWithMetadata
或
accessor

无论返回值怎样包装,发现语义仍是:

通过名字查询根服务引用。

但版本实现结论必须写准确:

Android 16 Java ServiceManager 主路径使用 getService2 / checkService2;
旧 getService / checkService 仍为兼容接口,不应当作当前唯一实现描述。

Java 缓存、客户端缓存与服务端注册表不是一处

Android 16 Java 层至少有两个不同缓存概念:

sServiceManager:
    缓存 IServiceManager 自己的类型化代理,
    避免反复从 handle 0 构造。

sCache:
    进程启动阶段可预填的一组服务 Binder 引用。

getService() 未命中 sCache 后会执行远端查询,但源码没有把每一次普通 rawGetService() 结果自动写回这个静态表。

所以不要写成:

ServiceManager.getService() 第一次远端查,之后所有服务都自动永久命中缓存。

缓存策略是版本相关实现,服务发现模型才是稳定机制。

把服务发现放回真实系统

ServiceManager 不是 Binder 引用的唯一来源

ServiceManager 提供“根服务发现”,但后续 Binder 引用还可以通过普通事务传播:

方法参数中的 callback;
方法返回值中的子接口;
token;
listener;
session 对象。

典型结构:

客户端先通过 ServiceManager 得到 IRootService;
调用 root.openSession();
reply 再返回 ISession Binder 引用;
后续客户端直接调用 session。

所以:

ServiceManager 是根目录;
Binder 引用传播可以继续构成对象图。

三组同名概念:SystemService、Binder Service 与 Manager

1. Android Service 组件
android.app.Service
bindService()
onBind()

它解决应用组件生命周期和连接;onBind() 可以返回 Binder,但 Android Service 本身不等于 Binder Driver 或 servicemanager 条目。

2. Binder Service
实现 IBinder / AIDL Stub 的远端服务端点

它解决跨进程接口调用。

3. SystemService
PowerManagerService
ActivityManagerService
WindowManagerService

它解决系统功能和状态治理,通常会发布 Binder Service,但 SystemService 生命周期对象本身不等于对外 Binder 接口。

再看三个 manager:

名称作用范围
ServiceManager / servicemanager跨进程 Binder 名字注册与查询
SystemServiceManagersystem_server 内系统服务启动和生命周期
LocalServicessystem_server 内部同进程服务引用

三个名字相似,解决的问题完全不同。

ActivityManager.getService() 的真实查询链

Android 16 的 ActivityManager.getService() 可以整理为:

IActivityManager am = IActivityManager.Stub.asInterface(
        ServiceManager.getService(Context.ACTIVITY_SERVICE));

这行代码同时包含两种转换:

"activity"
    ↓ ServiceManager
通用 IBinder
    ↓ IActivityManager.Stub.asInterface
类型化 IActivityManager

后续 am.xxx() 是直接面向 ActivityManagerService 的业务 Binder 事务,不需要 servicemanager 代转。

设备证据:名字存在不等于业务可用

连接设备后先列出名字注册表:

adb shell service list

再用 dumpsys 查询某个服务宿主 PID:

adb shell dumpsys --pid activity

两类证据回答的问题不同:

service list:
    名字是否注册,接口 descriptor 是什么。

dumpsys --pid:
    这个 Binder 服务当前由哪个进程承载。

这里必须区分两类 Demo:

实验获得首个 IBinder 的方式能验证的发现机制
仓库 BinderLabbindService()ServiceConnectionAndroid Service 组件绑定与跨进程 Binder 调用
platform / system 权限实验ServiceManager.addService() / getService()/dev/binder context manager、handle 0 与 servicemanager 注册表

因此 BinderLab 不能直接证明 Framework ServiceManager 的 handle 0、addService()getService() 或名字注册表;这需要 platform/system 权限环境或 Native Binder 服务实验。两条路径最终都能交付 IBinder,但根发现机制不同。

如果在具备相应权限的自建 ServiceManager Demo 中验证,至少记录:

addService 前 getService 是否为空;
addService 后返回 IBinder 的实际 class;
服务进程 PID;
后续业务 transact 是否直接到服务端 PID。

API 36 窄范围输出记录了 activity: [android.app.IActivityManager]service check activity 的 found 结果和非空宿主 PID,证明该 Framework 服务已注册并可解析到宿主进程(证据包没有自建 platform 服务的注册前/后对照;名字存在也不能证明引用仍存活、权限通过或业务状态正常)。

“拿不到服务”至少有四种失败形态

“拿不到可用服务”至少包含四种不同表现:

返回 null:服务尚未注册、名字错误或当前查询语义不等待;
抛出异常:权限拒绝、查询事务失败或版本实现返回错误;
持续等待:wait 路径仍在等服务发布或启动;
拿到引用但调用失败:旧 BinderProxy 已死亡、descriptor 或服务状态不匹配。

不能只写:

ServiceManager 坏了。

诊断要分别确认:

名字是否存在;
查询事务是否成功;
返回引用是否存活;
目标进程是否存在;
业务接口 descriptor 是否匹配。

从服务名到业务 Proxy

1. servicemanager 创建本地 Binder,并成为驱动 context manager;
2. handle 0 为当前 Framework Binder context 的客户端提供根发现入口;
3. 服务端把 name 和本地 Binder 引用作为 addService 事务参数发送;
4. 驱动为 servicemanager 建立对目标 node 的引用;
5. servicemanager 保存 name → sp<IBinder> / 元数据;
6. 客户端通过 get / check / wait service 概念路径查询;
7. 目标 Binder 引用通过 reply 传回,驱动为客户端建立自己的 binder_ref / handle;
8. Stub.asInterface() 把通用 IBinder 包成业务接口;
9. 后续业务调用直接去目标 node,不再经过 servicemanager。

ServiceManager 只负责把名字兑换成引用。拿到引用之后,执行现场就离开了名字表:事务进入目标进程,由 Binder 线程、业务自己的 Handler,以及可能出现的嵌套调用共同决定接下来的线程关系。

源码与官方文档