3. Binder机制基础
在Android的进程间通信体系中,Binder不是单一的一个类或一个接口,而是一套横跨用户态与内核态的完整通信基础设施。理解Binder,必须同时看清四个层面:内核中的Binder驱动、进程间传递的Binder协议、用户态可见的Binder对象,以及跨进程引用时使用的句柄、引用与代理。只有把这四个层面串起来,才能理解为什么一次startService()最终能调用到另一个进程中的onStartCommand()。
一、Binder驱动:内核中的通信枢纽
Binder驱动位于Linux内核空间,通常以/dev/binder字符设备的形式存在。所有使用Binder的进程都要先打开这个设备,再通过ioctl系统调用与驱动交互。驱动本身并不理解“Service”“Activity”这些Android概念,它只处理几类基本事务:进程上下文管理、内存映射、对象创建与查找、数据拷贝、线程调度和死亡通知。
Binder驱动最关键的设计是“一次拷贝”。传统管道或Socket通常需要两次数据拷贝:用户态到内核态,再从内核态到目标用户态。Binder通过mmap把内核缓冲区映射到接收进程的用户空间,发送进程把数据写入内核缓冲区后,接收进程可以直接读取,从而减少一次拷贝。这也是Binder在移动端高频IPC场景下性能较好的重要原因。
驱动还维护一张全局的Binder对象表,记录每个Binder节点属于哪个进程、有哪些引用、引用计数是多少。当某个进程退出时,驱动会根据引用关系通知相关进程,避免出现悬空引用。
二、Binder协议:跨进程调用的“语言”
Binder协议定义了进程与驱动之间、以及进程与进程之间如何表达一次调用。一次典型的Binder事务由binder_transaction_data描述,其中包含目标句柄、代码(code)、数据缓冲区、偏移量、安全上下文等信息。用户态通常不直接拼装这些结构,而是通过IBinder.transact()或AIDL生成的代理类完成封装。
协议中最重要的两个概念是BC_和BR_命令:BC_表示进程发给驱动的命令,BR_表示驱动返回给进程的命令。例如BC_TRANSACTION表示发起一次事务,BR_TRANSACTION表示驱动把事务投递给目标进程。目标进程处理完后,通过BC_REPLY返回结果,驱动再以BR_REPLY交回发起方。
// 简化的事务方向
发起进程 --BC_TRANSACTION--> Binder驱动 --BR_TRANSACTION--> 目标进程
发起进程 <--BR_REPLY-------- Binder驱动 <--BC_REPLY-------- 目标进程
协议还规定了数据对齐、对象偏移、文件描述符传递等细节。AIDL编译器生成的代码,本质上就是按照Binder协议把方法参数序列化到Parcel中,再交给驱动传输。
三、Binder对象、句柄、引用与代理
Binder对象是用户态对内核Binder节点的抽象。一个Binder对象可以是一个Binder实例,也可以是一个BinderProxy实例。前者是本地实现,后者是远端对象的本地代表。理解它们的关键在于区分“本地对象”和“跨进程引用”。
- Binder对象:实现了
IBinder接口,能够参与Binder事务。本地服务通常继承Binder类。 - 句柄(handle):驱动为跨进程引用分配的整数标识。同一个远端Binder对象在不同进程中可能有不同句柄值。
- 引用(reference):指某个进程持有对Binder对象的访问权。引用计数决定对象何时可以被释放。
- 代理(Proxy):用户态对象,内部持有句柄,方法调用时把参数打包并通过驱动发送到远端。
当进程A把本地Binder对象传递给进程B时,驱动会在B中创建一个BinderProxy,并分配一个句柄。B调用该代理的方法时,代理把句柄和参数交给驱动,驱动根据句柄找到A中的真实Binder对象,再把调用投递过去。整个过程对上层代码透明,AIDL接口看起来就像本地方法调用。
四、一次Service启动中的Binder调用链
以startService()为例,应用进程调用ContextImpl.startService(),最终会通过ActivityManagerProxy向system_server中的ActivityManagerService发起Binder调用。这里的ActivityManagerProxy就是客户端代理,它持有AMS的句柄;ActivityManagerService继承自IActivityManager.Stub,是服务端的本地Binder对象。
应用进程:
ContextImpl.startService()
→ ActivityManagerProxy.startService()
→ BinderProxy.transact(START_SERVICE_TRANSACTION, data, reply, 0)
→ ioctl(/dev/binder, BINDER_WRITE_READ)
system_server:
Binder驱动投递 BR_TRANSACTION
→ ActivityManagerService.onTransact()
→ 解析 Parcel,执行 startService 逻辑
→ 返回结果,BC_REPLY
这条链路中,应用进程看到的是代理,system_server看到的是真实Binder对象,驱动负责把句柄翻译成对象、把Parcel搬运到目标进程。ServiceManager本身也是一个特殊的Binder服务,它的句柄固定为0,负责把“服务名称”解析为“Binder引用”,供其他进程查询。
五、常见误区与复盘要点
| 误区 | 正确理解 |
|---|---|
| 句柄就是Binder对象的全局ID | 句柄只在单个进程内有效,不同进程对同一对象的句柄值可能不同 |
| BinderProxy就是远端对象本身 | BinderProxy只是本地代理,真正执行逻辑的是远端Binder对象 |
| Binder驱动理解AIDL接口 | 驱动只处理字节流和对象引用,接口语义由用户态Stub/Proxy解释 |
| 引用计数只影响内存 | 引用计数还决定驱动何时通知死亡、何时释放节点 |
| 一次拷贝意味着没有拷贝 | 发送方仍需把数据写入内核缓冲区,只是接收方无需再拷贝一次 |
TransactionTooLargeException,优先检查Parcel大小是否超过1MB左右限制;如果遇到DeadObjectException,说明远端Binder对象已死亡,通常需要重新获取服务或监听linkToDeath。
把Binder驱动、协议、对象、句柄、引用和代理放在一张图中理解,就能看清Android服务启动的底层路径:应用进程通过代理发起事务,驱动根据句柄找到目标Binder对象,目标进程在onTransact()中执行真实逻辑,再把结果沿原路返回。后续分析ServiceManager和服务注册时,这套对象与引用的映射关系会反复出现。