9. 服务注册流程:Server 端如何将自己注册到 ServiceManager?
好,咱们接着聊。前面几章我们把 ServiceManager 的启动和获取讲清楚了,现在该轮到 Server 端上场了。
说白了,服务注册就是 Server 进程把自己“挂到” ServiceManager 这个电话总机上,让其他进程能查到它。我当年第一次看这块代码时,觉得不就是调个 addService 嘛,有啥复杂的?结果一追下去,发现里面门道不少。
9.1 注册的入口:addService
不管你是写一个系统服务还是应用层服务,注册的入口都是同一个:
// 获取 ServiceManager 代理
sp<IServiceManager> sm = defaultServiceManager();
// 注册服务
sm->addService(String16("my_service"), new MyService(), false);
嗯,就这么三行。但你要知道,这三行背后牵动了 Binder 驱动、ServiceManager 进程、还有你自己 Server 进程的三方协作。
addService 的第三个参数 allowIsolated 我提一嘴,它控制是否允许隔离进程访问你的服务。我在项目中遇到过有人把这个设成 true,结果一些沙箱进程也能调他的服务,出了安全问题。所以,非必要别开。
9.2 调用链:从 Java 到 Native 再到内核
如果你是用 Java 写的服务,比如继承 Service 或者 SystemService,那调用链会长一点:
// Java 层
ServiceManager.addService("my_service", binder);
// 往下走,会调到 Native 层
// frameworks/base/core/jni/android_os_ServiceManager.cpp
// 再往下,就是 BpServiceManager::addService()
// frameworks/native/libs/binder/IServiceManager.cpp
我个人习惯直接看 Native 层的实现,因为 Java 层只是包了一层 JNI 调用,核心逻辑全在 Native 里。
9.3 BpServiceManager::addService 干了什么
来看关键代码:
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated) {
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service); // 这里!把你的 Binder 对象写进去
data.writeInt32(allowIsolated ? 1 : 0);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err;
}
注意 data.writeStrongBinder(service) 这一行。它把你的 Binder 对象序列化到 Parcel 里,然后通过 transact 发送给 ServiceManager。这里有个关键点:writeStrongBinder 不只是写一个句柄,它会在 Binder 驱动里创建一个对 Server 端 Binder 实体的引用。
核心理解:注册服务的本质,就是把 Server 端的 Binder 实体引用传递给 ServiceManager,让 ServiceManager 能记住“这个服务在哪个进程里”。
9.4 Binder 驱动的处理:关键中的关键
当 ServiceManager 收到 ADD_SERVICE_TRANSACTION 时,它会在自己的服务列表里插入一条记录。但真正有意思的是 Binder 驱动里发生了什么。
我画了一张图,帮你理清整个流程:
你看,Binder 驱动在这里扮演了“翻译官”的角色。它把 Server 端的 Binder 实体转换成一个 handle,然后把这个 handle 交给 ServiceManager。ServiceManager 不需要知道 Server 进程的具体地址,它只需要记住这个 handle 就行。
9.5 ServiceManager 内部:svclist 的维护
ServiceManager 收到请求后,会调用 do_add_service 函数:
int do_add_service(struct binder_state *bs, uint32_t handle, const char *name) {
struct svcinfo *si;
// 检查服务名是否已存在
si = find_svc(name);
if (si) {
// 已存在则释放旧的 handle
binder_release(bs, si->handle);
si->handle = handle;
} else {
// 新建一个 svcinfo 节点
si = malloc(sizeof(*si) + strlen(name) + 1);
si->handle = handle;
strcpy(si->name, name);
si->next = svclist;
svclist = si; // 头插法
}
return 0;
}
这里有个细节:svclist 是一个单向链表,新注册的服务插在头部。所以如果你注册了多个服务,查找时会从最新的开始匹配。不过一般服务名不会重复,所以这个顺序影响不大。
避坑指南:我曾经遇到过一个 bug,某个服务注册后一直查不到。追了半天发现是服务名里带了个不可见字符。ServiceManager 做的是字符串精确匹配,多一个空格都不行。所以,服务名一定要规范,建议全小写加下划线。
9.6 注册过程中的权限检查
你可能想问:是不是谁都能往 ServiceManager 里注册服务?
答案是否定的。ServiceManager 会检查调用者的 UID。只有系统进程(UID 为 AID_SYSTEM 或 AID_ROOT)才能注册系统服务。普通应用进程想注册?门都没有。
这个检查在 do_add_service 里:
if (uid != AID_SYSTEM && uid != AID_ROOT) {
// 非系统进程,拒绝注册
return -1;
}
嗯,就是这么简单粗暴。不过 Android 8.0 之后,ServiceManager 的权限模型有所调整,但核心逻辑没变。
9.7 注册完成后的状态
注册成功后,你的 Server 进程就进入了“等待被调用”的状态。它会进入一个 IPCThreadState 的循环,不断从 Binder 驱动读取请求:
// Server 端的主循环
IPCThreadState::self()->joinThreadPool();
这个 joinThreadPool 会让当前线程进入 talkWithDriver 循环,等待 Client 发来的请求。说白了,就是阻塞在 ioctl 上,等驱动来敲门。
9.8 总结一下注册流程
| 步骤 | 谁在做 | 做了什么 |
|---|---|---|
| 1 | Server 进程 | 调用 addService,传入服务名和 Binder 对象 |
| 2 | Binder 驱动 | 创建内核节点,将 Binder 实体转为 handle |
| 3 | ServiceManager | 收到 handle,存入 svclist 链表 |
| 4 | Server 进程 | 进入 joinThreadPool,等待 Client 请求 |
整个流程走下来,你会发现 Binder 驱动才是真正的核心。它负责了进程间通信的底层细节,让 Server 和 ServiceManager 能像在同一个进程里一样传递数据。
我个人觉得,理解服务注册的关键不在于记住那几个 API 调用,而在于明白 Binder 实体、引用、handle 这三者之间的关系。搞懂了这些,后面看服务查询、服务调用就会轻松很多。
注意:如果你在调试时发现服务注册失败,先检查三件事:1) 服务名是否正确;2) 进程是否有注册权限;3) Binder 驱动是否正常工作(看看 /dev/binder 是否存在)。这三板斧能解决 90% 的问题。
公众号:蓝海资料掘金营,微信deep3321