2、Binder通信机制(上):Binder原理概述、Binder驱动、ServiceManager启动与注册、Binder协议(BC/BR命令)
好,我们开始聊Binder。说实话,Binder是Android系统里最绕不开的一块硬骨头。我当年刚接触Framework时,被Binder折腾得够呛——明明就是个进程间通信,怎么搞得这么复杂?后来我才明白,Binder的设计哲学,其实比Linux原生的Socket、共享内存要优雅得多。
这一章我们先打地基。把Binder的原理、驱动、ServiceManager以及BC/BR协议讲透。你把这些搞明白了,后面看AMS、WMS的源码就会轻松很多。
2.1 Binder原理概述:为什么Android要自己搞一套IPC?
先问一个问题:Linux下进程间通信的方式那么多——管道、消息队列、共享内存、Socket,为什么Android还要自己造一个Binder?
我个人习惯从三个维度去理解这个问题:
- 性能:传统IPC需要两次数据拷贝(用户态→内核态→用户态),Binder只需要一次。因为Binder驱动在内核态做了内存映射,直接把发送方的数据映射到接收方的地址空间。说白了,少拷贝一次,速度就上去了。
- 安全性:传统IPC的接收方无法确认发送方的真实身份。Binder基于C/S架构,每个Binder实体都有一个唯一的handle,驱动会帮你做身份校验。我在项目中遇到过恶意App伪造Socket连接的情况,换成Binder后,这种问题基本杜绝了。
- 面向对象:Binder把远程调用封装成了方法调用。你调用一个远程对象的方法,就像调用本地方法一样。这种设计对上层应用非常友好。
嗯,这里要注意:Binder并不是万能的。它适合频繁、小数据量的跨进程调用。如果你要传大文件,还是得用共享内存或者FileDescriptor。
核心总结:Binder = 一次拷贝 + 安全校验 + 面向对象调用。这是Android选择Binder的根本原因。
2.2 Binder驱动:内核态的中枢神经
Binder驱动是Binder通信的基石。它运行在内核态,负责管理Binder实体、处理线程池、完成数据拷贝。你可以把它想象成一个快递中转站——所有跨进程的包裹都要经过它。
驱动的主要职责有四个:
- 管理Binder节点:每个Binder服务端在驱动里对应一个binder_node,客户端对应一个binder_ref。
- 处理BC/BR命令:BC是客户端发给驱动的命令,BR是驱动发给客户端的回复。后面会细讲。
- 内存映射:通过mmap把内核缓冲区映射到用户空间,实现一次拷贝。
- 线程管理:驱动维护了一个Binder线程池,当有请求进来时,会唤醒空闲线程去处理。
我曾经调试过一个性能问题:某个App频繁跨进程调用,导致Binder线程池耗尽,系统卡顿。后来发现是驱动默认的线程池大小(16个)不够用。调整了BINDER_MAX_THREADS后问题解决。所以,了解驱动的线程模型很重要。
驱动的工作流程大致如下:
客户端调用transact()
→ 驱动收到BC_TRANSACTION命令
→ 驱动查找目标binder_node
→ 驱动拷贝数据到目标进程的缓冲区
→ 驱动向目标进程发送BR_TRANSACTION
→ 目标进程处理完,驱动返回BR_REPLY
→ 客户端收到回复
你看,整个过程只有一次数据拷贝。这就是Binder高效的原因。
2.3 ServiceManager启动与注册:Binder世界的“黄页”
ServiceManager是Binder通信的“总机”。所有服务都要向它注册,所有客户端都要通过它查找服务。它本身也是一个Binder服务,它的handle是0——这是系统预定义的。
启动流程很简单:
- init进程启动servicemanager进程
- servicemanager打开/dev/binder设备
- 调用mmap分配内存
- 进入循环,等待客户端请求
注册服务的流程,我画了一张图帮你理解:
你想想看,如果没有ServiceManager,每个客户端都要硬编码服务地址,那系统就乱套了。ServiceManager的存在,让服务发现变得动态、灵活。
避坑指南:我曾经遇到过ServiceManager启动失败导致系统无法正常运行的bug。原因是/dev/binder设备节点权限不对。检查一下init.rc中servicemanager的启动配置,确保它有正确的设备访问权限。
2.4 Binder协议:BC/BR命令详解
Binder协议是客户端和驱动之间的通信语言。BC(Binder Command)是客户端发给驱动的命令,BR(Binder Return)是驱动发给客户端的回复。说白了,就是一套“请求-响应”的指令集。
常用的BC命令有这些:
| 命令 | 含义 | 说明 |
|---|---|---|
| BC_TRANSACTION | 发起一次跨进程调用 | 客户端调用transact()时触发 |
| BC_REPLY | 回复一次调用 | 服务端处理完请求后回复 |
| BC_FREE_BUFFER | 释放缓冲区 | 客户端收到数据后通知驱动释放内存 |
| BC_REGISTER_LOOPER | 注册Binder线程 | 线程加入Binder线程池时调用 |
对应的BR命令:
| 命令 | 含义 | 说明 |
|---|---|---|
| BR_TRANSACTION | 驱动通知服务端有请求 | 服务端收到后开始处理 |
| BR_REPLY | 驱动返回结果给客户端 | 客户端收到后继续执行 |
| BR_DEAD_BINDER | 通知Binder服务已死亡 | 客户端可以清理资源或重连 |
| BR_SPAWN_LOOPER | 驱动要求创建新线程 | 当线程池不够用时触发 |
我举个例子,你就能明白BC/BR是怎么配合的了。假设App A要调用App B的某个方法:
// App A(客户端)
BinderProxy.transact() → 发送BC_TRANSACTION到驱动
// Binder驱动
收到BC_TRANSACTION → 查找App B的binder_node
→ 向App B发送BR_TRANSACTION
→ 等待App B处理
// App B(服务端)
收到BR_TRANSACTION → 执行onTransact()
→ 发送BC_REPLY到驱动
// Binder驱动
收到BC_REPLY → 向App A发送BR_REPLY
// App A
收到BR_REPLY → 获取返回结果
你看,整个过程就是BC和BR交替进行。驱动在这里扮演了“调度员”的角色。
注意:BC/BR命令是同步的。客户端发送BC_TRANSACTION后会阻塞,直到收到BR_REPLY。如果你在UI线程做Binder调用,可能会引发ANR。我建议把耗时操作放到子线程去做。
2.5 小结:Binder通信的核心脉络
这一章我们讲了Binder的四个核心知识点:
- 原理:一次拷贝、安全校验、面向对象调用
- 驱动:内核态的中枢,管理节点、处理命令、内存映射
- ServiceManager:服务注册与发现的“黄页”
- BC/BR协议:客户端与驱动的通信语言
我个人觉得,理解Binder的关键在于“驱动”这个角色。它把复杂的跨进程通信封装成了简单的命令交互。你只要记住:BC是请求,BR是响应,驱动是中间人。
下一章我们会深入Binder的Java层实现,看看ServiceManager.addService()和BinderProxy.transact()在源码里是怎么写的。到时候你会看到,底层那些BC/BR命令,其实都被封装成了Java方法调用。
公众号:蓝海资料掘金营,微信deep3321