【多进程通信】Binder机制
这个类是Android中定义的一个类,它是能进行远程操作的对象的一个基类,一个轻量级的远程过程调用机制的核心部分在IBinder类中定义。这个类是一个IBinder类的实现,为创建一个本地实现对象提供了标准的支持。
大多数开发者都不会直接去实现这个类,而是用aidl工具去描述所需的接口,让它产生适当的子类。你可以从Binder类直接实现自定义的RPC协议,或者简单地实例化一个原始的Binder对象,用来能够跨进程共享的一个标志(token)。
1、Linux内核的基本知识
- 进程隔离/虚拟地址空间:进程间是不可以共享数据的,相当于被隔离,每个进程被分配到不同的虚拟地址中
- 系统调用:Linux内核对应用有访问权限,用户只能在应用层通过系统调用,调用内核的某些程序
- binder驱动:它负责各个用户的进程,通过binder通信内核来进行交互的模块
2、为什么使用Binder
- 性能上,相比传统的Socket更加高效
- 安全性高,支持协议双方互相校验
3、Binder通信模型
image- Service服务端通过Binder驱动在ServiceManager的查找表中注册Object对象的add方法
- Client客户端通过Binder驱动在ServiceManager的查找表中找到Object对象的add方法,并返回proxy的add方法,add方法是个空实现,proxy也不是真正的Object对象,是通过Binder驱动封装好的代理类的add方法
- 当Client客户端调用add方法时,Client客户端通过Binder驱动将proxy的add方法,请求ServiceManager来找到Service服务端真正对象的add方法,进行调用
带你简单理解Binder
Binder是Android中的一个类,它实现了IBinder接口。从IPC(Inter-Process Communication)角度来说,Binder是Android中一种跨进程通信方法,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从AndroidFramework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
1.既然是跨进程,那么Client端是如何准确找到相对应的Server端所在的进程?
2.有一定计算机基础的人都应该明白,两个进程间的内存是不共享的,那么Binder又是如何将对象从一个进程传递到另个一进程呢?
3.当Client持有了远端进程“对象”时,调用该对象具体函数,Server进程的对应函数会得到执行,这个也是解释不通的。
第一个问题:在AIDL转换成的java文件中,有一个常量DESCRIPTOR,这个常量是Binder的唯一标识,一般用当前的Binder的类名表示,通过这个常量就可以找到对应的进程。第二个问题:对象是通过序列化在不同进程间进行通信的,只要对象实现了Parcelable接口,就可以进行序列化,Parcelable具体使用不在本文讨论范围。第三个问题:通过第二个问题我们已经了解到,对象需要进行序列化,才能在两个进程间进行通信,那也就是说,两个进程拿到的对象并不是一个对象,回到第三个问题本身,要实现跨进程的调用,那么这两个对象之间肯定是有联系的,通过什么进行联系呢,肯定是要通过Binder来进行跨进程间的调用。
先看一下Client、Service、ServiceManager三者之间的关系:
Client、Service、ServiceManager三者之间的关系当Service创建的时候,首先在ServiceManager中注册一下地址,Client在ServiceManager中查找对应Service的地址,然后根据地址进行请求,Service做出响应。
上图简单介绍了以下三者时间的关系,当然真正的关系不是这么简单的,涉及到Framework层、JNI层、Native层和Kernel层
从上图中可以看到,真正实现跨进程通信是在Kernel层。我们平时说Binder相对于其他的进程间通信方式的优点是,在多进程间传递数据时,只会进行一次数据拷贝,这样的说法是是有歧义的。在Kernel层这样说是对的,但是从Framework到Kernel,Kernel到Framework都会进行内存拷贝。也就是上图的ioctl方法。
服务端跨进程的类都要继承Binder类。我们所持有的Binder引用(即服务端的类引用)并不是实际真实的远程Binder对象,我们的引用在Binder驱动里还要做一次映射。也就是说,设备驱动根据我们的引用对象找到对应的远程进程。客户端要调用远程对象函数时,只需把数据写入到Parcel,在调用所持有的Binder引用的transact()函数,transact函数执行过程中会把参数、标识符(标记远程对象及其函数)等数据放入到Client的共享内存,Binder驱动从Client的共享内存中读取数据,根据这些数据找到对应的远程进程的共享内存,把数据拷贝到远程进程的共享内存中,并通知远程进程执行onTransact()函数,这个函数也是属于Binder类。远程进程Binder对象执行完成后,将得到的写入自己的共享内存中,Binder驱动再将远程进程的共享内存数据拷贝到客户端的共享内存,并唤醒客户端线程。整个过程就完美解释了两个进程间为什么能够进行通信,所有的实现都是在Kernel层去实现,屏蔽了细节,我们只需要实现Binder接口就可以轻松的实现跨进程通信了。