Binder 通信
Binder 是什么,Android 为什么需要它
Binder 不等于 AIDL
| 常见误解 | 正确理解 |
|---|---|
| Binder 只是“让两个进程互相调用方法”的一个库 | Binder 是一套以内核驱动为核心的跨进程通信与对象管理机制 |
| Binder 就是 AIDL | AIDL 只是生成接口胶水代码的工具,Binder 才是通信机制本身 |
| 传统 socket 也能做,Android 为什么非要 Binder | 传统 IPC 无法同时满足性能、安全与线程/对象模型需求,Binder 是系统定制的方案 |
App 开发者通常从 AIDL 或 getSystemService() 接触 Binder。接口调用看起来与本地方法相同, 但对象实现可能位于另一个进程:Proxy 将参数写入 Parcel,Binder 驱动投递事务,服务端 Binder 线程再进入 Stub。AIDL 只负责生成其中的接口胶水代码。
因此,Binder 更准确的定义是:Android 提供的一套以内核驱动为基础的进程间通信(IPC)与 远程调用(RPC)机制。它同时处理事务投递、远端对象引用、调用方身份和对象死亡通知。
核心结论
- Binder 通信解决的是“跨进程怎么像本地调用一样安全、高效地调用”。
- 一次调用由客户端 Proxy 打包参数,Binder 驱动跨进程投递,服务端 Stub 拆包并转交业务实现。
- Binder 的优势集中在三点:性能(一次拷贝)、安全(内核校验 UID/PID 并衔接 SELinux)、 线程与对象模型(服务端线程池、死亡通知)。
- Android 中凡是跨进程的“调用方法拿结果”几乎都走 Binder:应用调系统服务、AIDL 服务、 ContentProvider、跨进程组件调用。
- ServiceManager 负责“按名字找到服务”,AIDL 负责“生成接口胶水代码”,它们都不是 Binder 本身。
Binder 解决了什么问题
Linux 本来就有管道、socket、共享内存、信号等 IPC,但 Android 的需要更苛刻:
- 性能:窗口刷新、输入事件、系统服务查询都是高频小请求,不能每次复制大量数据。
- 安全:内核要能校验发起方身份(UID/PID)并施加 SELinux 规则,而不是让任意进程冒充。
- 线程与对象模型:调用方要像本地方法一样发起请求;服务端要统一管理线程池;客户端还要 能感知“远端进程死了”。
Binder 通过“mmap + 一次拷贝”解决性能,通过事务携带 UID/PID 解决安全,通过 Binder 线程池 与死亡通知提供统一的调用和生命周期模型。这就是它被选为 Android 主 IPC 的原因。
与其他 IPC 的对比
| 对比项 | Binder | Socket | 共享内存 |
|---|---|---|---|
| 数据拷贝 | 一次(mmap 映射) | 至少两次 | 零拷贝但需要自行同步 |
| 身份校验 | 内核自动携带 UID/PID,可接 SELinux | 应用层自行实现 | 无 |
| 调用模型 | RPC + 线程池 + 死亡通知 | 自己管理连接与协议 | 只传数据,无调用语义 |
| 典型用途 | 系统服务、AIDL、应用组件 | 网络、流式数据 | 大块数据 |
结论不是“共享内存更好”,而是不同场景用不同工具:Binder 胜在调用语义和安全;跨进程传大块 数据时,常见做法仍是用共享内存配合 Binder 传递文件描述符(例如 Surface 的 Buffer)。
Binder 常用在哪些地方
按读者熟悉的场景排列:
- 应用调用系统服务:
getSystemService()拿到的 WindowManager、ActivityManager 等, 多数是应用进程里的 Proxy,真正实现跑在system_server,中间就是 Binder。 - AIDL 定义的自定义服务:应用通过 AIDL 把自己的服务暴露给其他进程,生成 Stub 与 Proxy。
- ContentProvider:跨进程访问数据时的 query、insert 等调用由 Binder 承载,大文件通过 Binder 传递文件描述符再读取。
- 应用组件跨进程:不同进程的 Activity、Service、Receiver 协作,以及 PendingIntent 等。
- 系统内部:Zygote 孵化进程、SystemServer 各服务、SurfaceFlinger、输入系统、媒体服务 都依赖 Binder。
- 进程内优化:同进程调用会走本地路径,不进驱动(详见 AIDL 章节)。
不需要背下所有场景。判断方法只有一句:两个进程之间要“调用一个方法并拿到结果”,基本就是 Binder。
一次调用全景:四个角色
- 调用线程发起接口调用:真正实现可能在另一个进程,调用方拿到的只是接口引用。
- Proxy 打包:把参数写进 Parcel,通过
transact()交给驱动。 - 驱动路由:校验身份、找到目标进程,把事务投递过去。
- Stub 分发:服务端 Binder 线程池中的线程取出事务,通过
onTransact()拆包并调用真实实现。 - 结果返回:reply 沿原路返回,客户端调用线程被唤醒。
这套模型是本系列的地图,后续每一章深入一层:驱动怎么投递、Parcel 里装了什么、 服务怎么被找到、胶水代码怎么生成、由哪个线程执行、进程死了怎么办、怎么排查。
源码入口与验证
本系列统一使用的稳定阅读入口:
frameworks/base/core/java/android/os/Binder.java:服务端本地对象与onTransact()。frameworks/base/core/java/android/os/BinderProxy.java:客户端代理与transact()。frameworks/base/core/java/android/os/Parcel.java:参数序列化容器。frameworks/native/libs/binder/IPCThreadState.cpp:Native 侧事务收发循环。drivers/android/binder.c:内核驱动主实现。
具体行号以 android-14.0.0_r75 源码检索结果为准,本系列不硬编码另一个 tag 的行号。
验证与排障
环境:主机终端连接已开启 USB 调试的设备;普通 user 版本可读范围受 SELinux 限制。
adb shell service list
adb shell dumpsys activity service
adb logcat -v threadtime | grep -E "Binder|TransactionTooLarge|DeadObject"service list 用来确认服务是否注册;dumpsys 用来观察服务状态;日志中的 TransactionTooLarge 与 DeadObject 分别指向“事务太大”与“远端进程死亡”。本章不展开 排查方法,系列末章会给出完整检查链。
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
Binder.java | onTransact() | 服务端事务分发入口 |
BinderProxy.java | transact() | 客户端发起跨进程事务 |
Parcel.java | write* / read* | 参数序列化与反序列化 |
IPCThreadState.cpp | transact() / talkWithDriver() | Native 层与驱动交换命令 |
binder.c | binder_ioctl() | 内核驱动入口 |
公共路径:Java 侧 frameworks/base/core/java/android/os/,Native 侧 frameworks/native/libs/binder/,内核侧 drivers/android/。行号以 r75 检索为准。
继续追踪 transact() 进入内核后的路径,见 Binder 驱动。