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 用一个预先约定的根端点打破这个循环。
注册与查询是两条相反的数据流
读图时必须分开两条链:
上半部分:服务注册,把 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/binder、hwbinder、vndbinder 和其他 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 名字注册与查询 |
SystemServiceManager | system_server 内系统服务启动和生命周期 |
LocalServices | system_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 的方式 | 能验证的发现机制 |
|---|---|---|
仓库 BinderLab | bindService() → ServiceConnection | Android 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,以及可能出现的嵌套调用共同决定接下来的线程关系。