Binder是Android中的一个类,它继承了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在linux中没有。从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,etc)和相应ManagerService的桥梁。
从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
为什么Android要选择Binder?
Linux也有一些很优秀的进程间通信机制,例如管道,消息队列,共享内存,socket等,但是为什么Android使用Binder而不是这些呢? 看Binder与共享内存,socket的对比。
Binder机制的工作流程
- 客户端获取服务端的带来对象(proxy)。我们需要明确的是客户端进程并不能直接操作服务端中的方法,如果要操作服务端中的方法,那么有一个可行的解决方法就是在客户端建立一个服务端进程的代理对象,这个代理对象具备和服务端进程一样的功能,要访问服务端进程中的某个方法,只需要访问代理对象中对应的方法即可;
- 客户端通过调用代理对象向服务端发送请求。
- 代理对象将用户请求通过Binder驱动发送到服务器进程;
- 服务端进程处理客户端发过来的请求,处理完之后通过Binder驱动返回处理结果给客户端的服务端代理对象;
- 代理对象将请求结果进一步返回给客户端进程。
通过以上5个步骤,就完成了一次Binder通信。
Binder机制的组成
Binder机制由三部分组成,即:
1.Client;
2.Server;
3.ServiceManager。
三部分组件之间的关系:
- Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的;
- 在Binder通信中,Server进程先注册一些Service到ServiceManager中,ServiceManager负责管理这些Service并向Client提供相关的接口;
- Client进程要和某一个具体的Service通信,必须先从ServiceManager中获取该Service的相关信息,Client根据得到的Service信息与Service所在的Server进程建立通信,之后Clent就可以与Service进行交互了;
- Binder驱动程序提供设备文件/dev/binder与用户空间进行交互,Client、Server和ServiceManager通过open和ioctl文件操作函数与Binder驱动程序进行通信;
- Client、Server、ServiceManager三者之间的交互都是基于Binder通信的,所以通过任意两者这件的关系,都可以解释Binder的机制。
Android Binder开发技术,摘要自《Android核心技术手册》笔记文档。点击可以查看详细类目。
Binder通信原理
1、动态内存可加载模块
我们之前提到的传统Linux IPC机制如管道,Socket套接字都是属于内核的一部分,通过内核的支持来实现跨进程通信,但是Binder并不是Linux系统内核的一部分。得益于Linux的 动态内核可加载模块 机制,Android系统通过动态的将Binder驱动添加到内核空间链接到内核作为其一部分运行。
2、内存映射
传统的 IPC 机制如管道、消息机制、Socket 都是需要拷贝两次数据
copy_from_user() //将数据从用户空间拷贝到内核空间
copy_to_user() //将数据从内核空间拷贝到用户空间
(共享内存不需要拷贝数据),而Binder数据拷贝只需要一次。这涉及到内存映射
Binder机制中的内存映射是通过mmap() 来实现的,简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
一次完整的 Binder IPC 通信过程通常是这样:
- 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。