欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

带你简单理解Binder

程序员文章站 2022-04-13 11:30:45
...

前言

我们看过很多关于Binder的文章,但是看完大多数文章后,都会有不知所云的感觉,是因为那些文章不够好吗?不是那些文章讲得不够好,我们看的不明白主要是存在两种情况,一种深入代码细节不能自拔,从FrameWork到Kernel层,长篇累牍,让人很难理解Binder;另一种是只讲framework层,Binder驱动并没有具体提到,导致我们会用Binder,也大致能说的出一些原理,可并没有一个完整的深刻认知。所以我不打算深入讨论Binder的底层细节,也不会贴出一大堆代码,带着大家一起看代码,因为有很多书和文章在这方面做的已经很细致了,这篇文章是从自己的角度,解读一下Binder机制,给大家一个简单清晰的解释。

为什么使用Binder

Android使用的Linux内核拥有着非常多的跨进程通信机制,比如管道,System V(消息队列、信号量和共享内存),Socket等;为什么还需要单独搞一个Binder出来呢?主要有两点:

  • 性能
  • 安全

在移动设备上,广泛的使用跨进程通信肯定对通信机制本身提出了严格的要求;Binder相对传统的Socket方式,更加高效;另外,传统的进程通信方式对于通信双方的身份并没有做出严格的验证,只有在上层协议上进行架设;碧柔Socket通信ip地址是客户端手动填写的,都可以进行伪造;而Binder机制从协议本身就支持对通信双方做身份校验,因而大大提升了安全性。这也是Android权限模型的基础。

什么是Binder

《Android开发艺术探索》一书中对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的服务。

上面的介绍,很多人看过之后,如果不理解,可能看过之后就会忘掉,我们暂时可以把Binder理解为Android中跨进程通信的一种方式。接下来先要从几个问题入手,来带领大家去理解Binder机制:

  1. 既然是跨进程,那么Client端是如何准确找到相对应的Server端所在的进程?
  2. 有一定计算机基础的人都应该明白,两个进程间的内存是不共享的,那么Binder又是如何将对象从一个进程传递到另个一进程呢?
  3. 当Client持有了远端进程“对象”时,调用该对象具体函数,Server进程的对应函数会得到执行,这个也是解释不通的。

  • 第一个问题:在AIDL转换成的java文件中,有一个常量DESCRIPTOR,这个常量是Binder的唯一标识,一般用当前的Binder的类名表示,通过这个常量就可以找到对应的进程。
  • 第二个问题:对象是通过序列化在不同进程间进行通信的,只要对象实现了Parcelable接口,就可以进行序列化,Parcelable具体使用不在本文讨论范围。
  • 第三个问题:通过第二个问题我们已经了解到,对象需要进行序列化,才能在两个进程间进行通信,那也就是说,两个进程拿到的对象并不是一个对象,回到第三个问题本身,要实现跨进程的调用,那么这两个对象之间肯定是有联系的,通过什么进行联系呢,肯定是要通过Binder来进行跨进程间的调用。

首先我们看看我们的程序跨进程调用系统服务的简单示例,实现浮动窗口部分代码:

//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);  
//布局参数layoutParams相关设置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);  
//添加view
wm.addView(view, layoutParams);

系统服务都是运行在systemServer进程中,因此我们调用系统服务都是跨进程的调用。第2行代码中,得到的wm是WindowManager对象的引用,第6行调用WindowManager的addView函数,将触发远程调用,调用的是运行在systemServer进程中的WindowManager的addView函数。
从上面代码可以看出来,创建一个Window是很简单的事,只需要通过WindowManager即可完成,WindowManager是外界访问Window的入口,但是具体的实现,还是要通过WindowManagerService来实现,WindowManager和WindowManagerService是一个挂进程的交互,是通过Binder实现的。

Binder机制

先看一下Binder的工作机制
带你简单理解Binder
当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法很耗时,那么就不能在UI线程中发起此远程请求,以防ANR;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方法实现,因为它已经运行在一个线程中了。

客户端Client调用Service的函数Func,执行的流程如下:
带你简单理解Binder

通过上面的两张图,应该能够清晰的了解到整个Binder的执行过程了

Binder的架构

上面一节我们对远程进程调用代码执行过程有个初步了解,在Android开发中,我们大量使用到了系统Service,比如媒体播放、各种传感器以及WindowManagerService等等。那么Android是怎么管理这些服务,并且让用户跨进程调用这些服务呢?首先我们看看调用系统服务的过程。在Android开机启动过程中,Android会初始化系统的各种Service,并将这些Service向ServiceManager注册(即让ServiceManager管理)。客户端想要得到具体的Service直接向ServiceManager要即可。客户端首先向ServiceManager查询得到具体的Service引用,然后通过这个引用向具体的服务端发送请求,服务端执行完成后就返回。

先看一下Client、Service、ServiceManager三者之间的关系:

带你简单理解Binder

当Service创建的时候,首先在ServiceManager中注册一下地址,Client在ServiceManager中查找对应Service的地址,然后根据地址进行请求,Service做出响应。
上图简单介绍了以下三者时间的关系,当然真正的关系不是这么简单的,涉及到Framework层、JNI层、Native层和Kernel层

带你简单理解Binder

从上图中可以看到,真正实现跨进程通信是在Kernel层。我们平时说Binder相对于其他的进程间通信方式的优点是,在多进程间传递数据时,只会进行一次数据拷贝,这样的说法是是有歧义的。在Kernel层这样说是对的,但是从Framework到Kernel,Kernel到Framework都会进行内存拷贝。也就是上图的ioctl方法。

Binder驱动实现原理

上述的第三个问题,也就是Binder驱动实现的原理,只要能知道上述答案,那下面就不需要看了。重新回顾一下这个问题:客户端持有远程进程的某个对象引用,然后调用引用类中的函数,远程进程的函数就执行了。学过操作系统的同学都知道,不同的进程之间是不共享资源的。也就是说,客户端持有的这个对象跟远程进程中的实际对象完全是两个不同的对象。客户端调用引用的对象跟远程进程中的对象不是同一个,那问题就来了,为什么调用客户端的这个远程对象的引用,远端进程相应的方法就会被调用?先看下面这幅图:

带你简单理解Binder

服务端跨进程的类都要继承Binder类。我们所持有的Binder引用(即服务端的类引用)并不是实际真实的远程Binder对象,我们的引用在Binder驱动里还要做一次映射。也就是说,设备驱动根据我们的引用对象找到对应的远程进程。客户端要调用远程对象函数时,只需把数据写入到Parcel,在调用所持有的Binder引用的transact()函数,transact函数执行过程中会把参数、标识符(标记远程对象及其函数)等数据放入到Client的共享内存,Binder驱动从Client的共享内存中读取数据,根据这些数据找到对应的远程进程的共享内存,把数据拷贝到远程进程的共享内存中,并通知远程进程执行onTransact()函数,这个函数也是属于Binder类。远程进程Binder对象执行完成后,将得到的写入自己的共享内存中,Binder驱动再将远程进程的共享内存数据拷贝到客户端的共享内存,并唤醒客户端线程。整个过程就完美解释了两个进程间为什么能够进行通信,所有的实现都是在Kernel层去实现,屏蔽了细节,我们只需要实现Binder接口就可以轻松的实现跨进程通信了。

Binder机制运用

好了,现在对Binder机制已经理解了,我们再看看Android是怎么运用Binder的。再现前面代码:

//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);  
//布局参数layoutParams相关设置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);  
//添加view
wm.addView(view, layoutParams);

这段代码前面已经出现过。getSystemService(getApplication().WINDOW_SERVICE);函数内部原理就是向ServiceManager查询标识符为getApplication().WINDOW_SERVICE的远程对象的引用。即WindowManager对象的引用,这个引用的真正实现是WindowManager的某个代理。得到这个引用后,在调用addView时,真正的实现是在代理里面,代理把参数打包到Parcel对象中,然后调用transact函数(该函数继承自Binder),再触发Binder驱动的一系列调用过程,在Binder驱动实现原理一节中有具体介绍,忘记了的同学可以返回继续看。关于Binder的代理对象,可以参考AIDL工具生成的代码,这里不再具体介绍。

相关标签: Binder