Android Binder 详解
1. 什么是 Binder?
Binder 是 Android 系统中最核心的跨进程通信(IPC)机制 ,负责几乎所有系统服务(如 ActivityManager、WindowManager)与应用程序之间的通信。它基于客户端-服务器模式,具有高性能、安全性高的特点。
2. 为什么需要 Binder?
Android 基于 Linux 内核,Linux 已有多种 IPC 方式(管道、Socket、共享内存等),但 Google 选择开发 Binder,原因如下:
| 特性 | Binder | 传统 IPC(如 Socket) |
|---|---|---|
| 性能 | 高(仅一次内存拷贝) | 较低(两次拷贝) |
| 安全性 | 支持 UID/PID 验证 | 需手动管理 |
| 同步模型 | 同步调用,类似函数调用 | 异步或需额外封装 |
| 实现复杂度 | 较高 | 较低 |
核心优势 :Binder 在传输数据时,只拷贝一次(从发送方用户空间到内核空间,接收方映射同一块内存),而传统方式通常需要两次拷贝。
3. Binder 通信模型
采用经典的 C/S(客户端-服务器)架构,包含四个角色:
css
[Client] <---> [Binder Driver (内核)] <---> [Server]
↑
|
[Service Manager]
- Client:服务的使用者(如 App 请求 AMS)
- Server:服务的提供者(如 AMS、WMS)
- Binder Driver:内核驱动(/dev/binder),负责数据中转、线程管理
- Service Manager:服务注册与查询的"电话总机"
4. 工作流程
步骤拆解:
-
Server 注册
Server 向 Service Manager 注册服务,告诉 SM:"我提供了 XX 服务,这是我的 Binder 引用"。
-
Client 获取代理
Client 向 SM 查询服务,SM 返回一个 Binder 代理对象(实际上是句柄)。
-
Client 调用方法
Client 调用代理对象的接口方法(如
activityManager.startActivity())。 -
数据打包与发送
Binder 驱动将参数序列化(Parcel),从 Client 进程拷贝数据到内核空间,并找到目标 Server 进程。
-
Server 执行
内核唤醒 Server 进程的 Binder 线程池,将数据解包并调用实际实现。
-
返回结果
Server 执行完成后,结果沿原路返回给 Client。
关键点 :对于 Client 来说,调用远程服务方法就像调用本地函数一样(透明性),但实际上线程会阻塞等待返回。
5. 一次数据拷贝原理
Binder 性能高的核心在于内存映射(mmap):
- 内核为每个 Server 进程预先映射一块内存(Binder 驱动管理的缓冲区)
- 当 Client 发送数据时,数据直接从 Client 用户空间 → 内核空间(第一次拷贝)
- 由于 Server 的
mmap将同一块内核内存映射到自己的用户空间,无需再次拷贝数据 - Server 直接读取自己的用户空间地址即可获得数据
arduino
传统 IPC:Client 用户空间 -> 内核空间 -> Server 用户空间 (2次)
Binder: Client 用户空间 -> 内核空间 (Server 通过 mmap 直接可见) (1次)
6. Binder 通信协议
6.1 数据包格式(Binder 协议)
Binder 驱动识别的协议分为:
- BC 协议(Binder Command):Client/Server → Driver 的命令
- BR 协议(Binder Return):Driver → Client/Server 的返回
常用命令:
BC_TRANSACTION:发送请求数据BR_TRANSACTION:接收请求数据BC_REPLY:发送响应数据BR_REPLY:接收响应数据
6.2 Parcel(数据序列化)
Binder 使用 Parcel 类完成数据的打包与解包,支持基本类型、Binder 对象、文件描述符等。
cpp
// C++ 示例
Parcel data, reply;
data.writeInt32(0);
data.writeString16("com.example.MyService");
remote->transact(CODE_START, data, &reply);
7. Binder 架构层次
从底层到上层:
| 层次 | 组件 | 语言 | 作用 |
|---|---|---|---|
| 内核层 | Binder Driver | C | 驱动,处理通信核心 |
| Native 层 | libbinder(BBinder/BpBinder) | C++ | 提供 Binder 框架基础类 |
| Native 层 | ServiceManager | C++ | 服务注册与查找 |
| Framework 层 | Binder.java / IBinder | Java | JNI 调用 Native 接口 |
| Framework 层 | AIDL | Java | 自动生成跨进程通信接口代码 |
核心类对应关系:
- IBinder:Binder 对象的基础接口
- BpBinder(Proxy):客户端代理
- BBinder(Native):服务端真实对象
- Binder(Java):BBinder 的 Java 封装
8. 代码示例
AIDL 定义(IHello.aidl):
java
interface IHello {
String sayHello(String name);
}
生成的 Stub 类结构:
scss
IHello.Stub (服务端)
├── onTransact() // 接收客户端请求
└── asInterface() // 返回服务端或代理
IHello.Stub.Proxy (客户端自动生成的代理)
└── sayHello() // 内部调用 transact()
服务端实现:
java
private final IHello.Stub mBinder = new IHello.Stub() {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
};
客户端调用:
java
IHello service = IHello.Stub.asInterface(binderProxy);
String result = service.sayHello("Android"); // 跨进程调用
9. 线程模型
- 每个 Server 进程在初始化时会启动 Binder 线程池(默认 16 个线程)
- 当 Client 请求到来时,Binder 驱动会从线程池中分配一个线程执行请求
- 如果线程池耗尽,请求会被挂起等待
注意事项 :
服务端实现不能做耗时操作,否则会占用 Binder 线程,导致其他客户端阻塞。耗时任务应另起线程处理。
10. 安全机制
- UID/PID 验证:Binder 驱动会自动记录调用者的进程 ID 和用户 ID
- 权限检查 :服务端可调用
Binder.getCallingUid()获取调用者身份,判断是否有权限 - 不可伪造:调用方的 UID/PID 由内核提供,无法在用户态伪造
11. 常见问题
Q1:Binder 传输数据大小有限制吗?
有 ,默认最大为 1MB (BINDER_VM_SIZE)。传输大数据(如图片)应使用共享内存(MemoryFile)或文件(ContentProvider)。
Q2:为什么 Android 用 Binder 而不是 D-Bus?
- D-Bus 基于 Socket,性能较低
- D-Bus 设计用于桌面环境,没有 UID/PID 传递
- Binder 更轻量,适合移动设备资源受限的场景
Q3:ServiceManager 的句柄为什么总是 0?
Service Manager 本身的 Binder 句柄固定为 0,任何进程都可以通过 Binder.getService("manager") 获取它。
12. 总结
| 方面 | 总结 |
|---|---|
| 定义 | Android 的核心跨进程通信(IPC)机制 |
| 模型 | 客户端-服务器模型,含 Binder 驱动和 Service Manager |
| 性能 | 一次内存拷贝,传输效率高 |
| 安全性 | 内核级 UID/PID 校验,天然支持权限控制 |
| 使用方式 | 通过 AIDL 自动生成代码,开发简单 |
Binder 不仅仅是 IPC 工具,更是整个 Android 系统服务的 "血管",理解它是深入 Android 系统开发的必经之路。