Android开发——如何理解晦涩的Binder机制

0.  前言

Android中Binder机制的重要性不言而喻,从IPC角度来看, Binder是一种跨进程通信方式,Binder也可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder;从Android framework角度来说,BinderService Manager连接各种Manager和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时服务端会返回一个包含了服务端业务的Binder对象,用于客户端获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

那么接下来让我们一起学习Binder吧,相信接下来的内容会让你有一定的收获~转载请注明出处为SEU_Calvin的博客

 

1.  什么是Binder

什么是Binder,应该怎么去理解Binder呢?首先介绍Binder为了实现进程之间的通信需要解决哪些问题:

//1.如何知道客户端需要调用哪个进程以及该进程中的方法
//2.客户端如何将函数形参发送给远程进程中的函数,以及如何将远程进程函数计算结果返回客户端
//3.如何去屏蔽底层通信细节,让实现客户端调用远程函数就像调用本地函数一样

第一个问题很容易解决,只要给每个需要远程通信的类一个唯一标识,可以通过包名+类名的字符串以标识,然后在类里面给每个函数编号即可对函数唯一编码。

第二个问题,定义一个可打包的接口Parcelable,这个接口提供2个重要函数,分别是将对象中的属性写入到数组和从数组中的数据还原对象,每个可以发送到远程函数作为形参的对象只需实现Parcelable接口即可。Parcelable具体使用可以参考Android开发——进程间通信之AIDL(二)

第三个问题,为了屏蔽进程之间的通信细节,那么Android团队肯定想定义一个类,由这个类来实现这些细节。这个类应该做哪些事情呢?首先,这个类得帮用户发送远程请求并将返回结果提交给用户,这是最重要的功能了,有了这个功能,妈妈再也不用担心我的进程间通信了。其次,如果我想实现服务端,什么时候客户端调用我,这些细节不用用户操心。当然,这个类还要帮用户封装更多细节。既然打算定义这个类了,那总得取个响当当的名称吧,什么?你说取名为Binder,好吧,那就叫Binder吧。服务端想要实现被跨进程访问,就必须继承Binder类。

有一点很重要,就是客户端发起远程服务调用时当前线程会被挂起,因此如果远程进程是执行长时间的运算,请不要使用主线程去调用远程函数,以防止ANR

 

2.  Binder的实现机制

说起Binder机制,下面这张图就是经典中的经典了。

2.1  Binder驱动

Binder驱动是一段运行在内核空间的代码,它是Binder机制的核心,负责Binder节点的建立,和进程间的信息传递。通过"/dev/binder"的文件在内核空间和用户空间来回搬数据。Linux ioctl 函数实现了从用户空间转移到内核空间的功能。

 

2.2  ServiceManager

ServiceManager是Init程序启动的守护进程之一,它是AndroidBinder通信机制的基础

ServiceManager提供了注册,检索服务的功能。即这些服务ServiceManager来统一进行管理。

 ServiceManager启动的过程做了三件事:

1)通过open打开设备文件/dev/binder,把文件内容映射到内核空间中,由于内核空间是所有进程共享的区域,所以借助这块区域,可以实现进程间通信。

2通过IO控制命令BINDER_SET_CONTEXT_MGR将当前进程注册到Binder驱动中,Binder驱动便会为他在内核空间创建一个Binder实体,并且将其句柄设置为0,从句柄值也能看出来,这个节点在Binder驱动中是唯一的,系统会将跨进程服务注册到它的服务列表里面,服务列表中存储的便是服务名字+这个服务在Binder驱动中大于0的句柄

3)最终ServiceManager也是会进入一个无限循环,等待客户端的IPC请求

2.3   服务注册

Zygote进程会孵化出的第一个子进程就是SystemService,我们大部分的系统服务比如ActivityManagerServiceWindowManagerService等都是该进程内的一个线程,这些服务会通过调用ServiceManager.addService方法添加到ServiceManager中的服务列表中。

(1)Server首先将自己作为对象,并且附上一个句柄为0的值(用于访问ServiceManager),并将这些内容封装成一个数据包后通过open Binder的设备文件/dev/binder发送给Binder驱动。
(2)Binder驱动首先会查看是否有该Server对应的Binder实体,如果没有就创建Binder实体并赋予一个大于0的句柄,接着会把这个句柄和服务名字等信息发送给ServiceManager。
(3)ServiceManager会首先查看服务列表中是否已经存在这个服务,不存在的话,就把这个服务和它的Binder驱动中的句柄加入到服务列表中。

 

2.4   服务调用

4Client想调用某服务时,会把该服务的名字,加上一个句柄为 0 的值封装为一个数据包并发送给Binder驱动。

5Binder驱动接收到句柄为0,就会将这数据包扔给ServiceManager

6ServiceManager接收到数据包后就会找某个名字的服务,然后将对应服务的句柄发送给Binder驱动,再发送回客户端。

7Client获取句柄之后,会利用这个句柄信息在自己本地创建一个远程Server的代理,以后Client发消息都是发给这个代理的,随后的通信便变成了代理通过Binder驱动与真正的Server进行交互了。

客户端调用这个服务时,会加上参数、标识符(标记远程对象及其函数)等数据放入到Client的共享内存Binder驱动根据这些数据找到对应的远程服务进程的共享内存,把数据拷贝到内核空间,并映射到远程服务进程中并通知远程进程执行onTransact()函数,远程进程Binder对象执行完成后将结果写入自己的共享内存中,Binder驱动再将结果数据拷贝到内核空间,并映射到客户端进程中,并唤醒客户端线程。

 

3  Android为什么要使用Binder机制

3.1  性能

Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次(共享内存方式一次内存拷贝都不需要)

3.1.1  Binder如何实现的一次拷贝

Linux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数,只有从用户空间拷贝到内核空间的函数(copy_from_user),和反过来的copy_to_user()函数。所以说,为了实现用户空间到用户空间的拷贝,mmap()分配的内存除了映射到接收方进程里,还映射到内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的原因。

3.2  方便性

Binder是基于客户端/服务器架构的,Server端与Client端相对独立,在客户端这边实现了一个代理,而在服务端通过线程池的方式来响应请求。客户端能够很方便地、像调用本地方法一样调用远程方法。

3.3  安全性

传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而在Android中使用Binder进程间通信时,需要对发送方和接收方的UID/PID进行验证,保证通信信息的安全性。

感谢:

http://blog.csdn.net/qianhaifeng2012/article/details/51602105

http://blog.csdn.net/linmiansheng/article/details/37918333

https://www.zhihu.com/question/39440766/answer/89210950

http://blog.csdn.net/huachao1001/article/details/51504469

发布了142 篇原创文章 · 获赞 1456 · 访问量 168万+

猜你喜欢

转载自blog.csdn.net/SEU_Calvin/article/details/54171151