Android中Binder机制实现进程间通信

来源:互联网 发布:淘宝客微信推广工具 编辑:程序博客网 时间:2024/05/29 19:39



           基本上不管是何种开发都会涉及到进程间通信的问题,即IPC,而安卓系统的IPC方式主要是Binder,先列举几种IPC的方式,对比Binder看看。


      Linux里几种比较常见的IPC方式比如有:共享内存,信号,Socket,管道(pipe),消息队列等。


     先从性能上说起:


     其中共享内存,顾名思义就是多个进程共享一块内存,大家有什么改变就直接在这块共同内存里改,这样看起来十分方便,数据也无需任何的拷贝,但是共享一块内存,安全性上显然会出很多问题,必须做好进程同步等问题,所以控制起来比较复杂。

   

     而Socket,即套接字,我们一般都是在网络请求,网络通信的时候使用它,如果使用它来实现同一机器上的进程间通信,由于他传输效率较低,显然开销会很大,不适合我们这里所说的进程间的通信。


    其次就是管道,消息队列等,利用他们实现传输通信,都需要把数据先从发送方的缓存区copy到内核开辟的缓存区,然后再从内核开辟的缓存区copy到接收方的缓存区,这样经过两次的copy才能实现进程间的通信,所以传输性能上并不优秀。


      再从安全性上考虑:


     传统的IPC,并没有给发送方进程分配UID和PID(即用户ID和进程ID),而是在发送的时候由用户填入数据包,这就给程序留下风险,即存在数据在放松过程中PID被修改的可能,也就是接收方无法获取可靠的发送方的UID和PID(无法鉴定身份),而安卓为每一个应用程序到分配好UID,这样当这个进程产生时他就已经有了一个唯一的PID,所以是可靠的。


    从以上的对比 我们可以看出Binder相较于其他IPC方式的优点,接下来就来看看Binder机制是如何实现进程间的通信的。


    先来贴一张图:

   


这个就是通过Binder实现服务端进程与客户端进程之间通信的这么一个架构。


其中包括三部分,服务端,Binder驱动,客户端。


Binder服务器端:就是一个继承于Binder的对象,它里面定义实现了各种方法(服务)

Binder驱动:当服务器端创建一个Binder对象的时候,Binder驱动里会相应的创建一个mRemote对象,和服务端一样,都是个                                Binder对象。

客户端:客户端可以通过获取Binder驱动的mRemote对象的引用,然后就可以调用Binder对象的服务了。


Binder机制下的两个进程之间的通信基本就是上面那样一个过程:即服务端定义服务,客户端通过获取服务端的对象引用的方式(通过中介Binder驱动),然后就可以调用服务端定义的各种服务。


然后还有一个问题:那么客户端是如何获取到这个Binder引用的呢


先来看看我们自定义一个service的时候,如何来和这个service通信:

服务端定义Servcie的时候,里面一般会定义个Binder类(这个Binder才是真正提供服务的),然后Service内部的OnBind()方法会把这个Binder返回,客户端调用bindService(),即与服务端的服务绑定,会调用到服务端的onBind()方法,而bindService()这个方法里面有个参数是ServiceConnnection mConnection,客户端创建一个ServiceConnection就可以在里面会获取到连接后服务端Binder的

</pre><span style="font-size:14px;">引用,从而实现客户端获取到服务端Binder引用,下面我们看一下客户端ServiceConnection:</span><p></p><p></p><pre code_snippet_id="1655977" snippet_file_name="blog_20160421_2_4458442" name="code" class="html"><span style="font-size:14px;"><span style="font-size:18px;">private ServiceConnection mConnection = new ServiceConnection() {      // 当与service的连接建立后被调用      public void onServiceConnected(ComponentName className, IBinder service) {  //注意这里的IBinder即获取到的Binder引用                   //获取到Binder引用后就可以调服务端的服务了    }        // 当与service的连接意外断开时被调用      public void onServiceDisconnected(ComponentName className) {              }  };  </span></span>



看了上面这些我们应该知道,Binder机制实现进程间通信的关键就是获取到服务端的代理,只要获取到服务端的代理,那么就可以调用服务端的方法,实现通信,所以关键所在就是这个代理如何获取,接下来看看:


安卓里面管理这些服务的有一个东西叫做ServiceManager(他本身也是个Service),当我们想要获取某个服务的代理时,就必须通过他,ServiceManager掌握着所有Service的句柄,即你只要找他,他就能给你找到你要的service的句柄,然后有了句柄就可以得到BpBinder(Binder代理)了,也就是可以获取到你想要的服务的代理了,下面是典型的获取服务端service代理的过程(获取AMS代理的过程):

IBinder b=ServiceManager.getService("activity");

然后转化为AMS代理:

IActivityManager am=asInterface(b);


可以知道,如果想要获取某个服务端的代理,那么通过ServiceManager就十分简单了,但是有一个问题:serviceManager本身也是一个service,那么他自己是如何被客户端获取的呢,因为只有先获取了ServiceManager我们才能通过他很方面的来获取其他的service.上面我们知道服务端代理的创建过程是我们先拿到句柄,然后就可以创建它,而ServiceManager默认句柄为0,即已经为他预定了一个句柄,所以他只要通过new BpBinder(0)就可以得到BpBinder,也就是Binder代理,而其他的服务,你无法知道句柄值是多少,你只能通过ServiceManager了。


上面介绍了客户端获取服务端代理的过程,那么服务端也是需要和客户端进行通信的,即服务端也想调客户端的方法,也就是服务端如何获取客户端的Binder代理呢,因为既然客户端已经可以与服务端通信了,可以调服务端的方法了,那么客户端就可以把自己这边的Binder代理作为参数传给服务端端了,这也是另一种获取Binder代理的方法。这样双方进行双向的通信就没有问题了,各自持有对方的Binder代理,通信就非常容易了。下面放一张网上的图来梳理一下这种双向通信:



再贴一张网上的整个Binder的原理图:






下面举个很形象简单明了的例子来理解一下Binder机制:

总的来说Binder分为一下四个部分:

1.client客户端    2.service服务端   3.Binder驱动  4.ServerManager

用我们平常网页请求服务器数据的例子再形象不过了,

浏览器就相当于这里的client客户端,服务器就相当于service服务端,而路由器就相当于这里的Binder驱动(负责传输),而DNS服务器就相当于这里的ServerManager(手里握有每个server的句柄和实体的映射表)。这应该很明白了。




下面详解一下这四个部分的配合完成跨进程传输的全过程:


首先我们知道所有想要获取service的客户端都是向serviceManager要(这就像我们想要访问一个网页,需要先通过DNS服务器解析地址一样),serviceManager本身也是一个service,所以我们先搞懂我们是如何获取到serviceManger的代理对象的?


  我们知道客户端与服务端Binder通信,实际上上是客户端获取到服务器端service的一个代理对象,而这一步一般分两步完成,先是获取到服务器端的BpBinder对象,然后调用asInterface()转化为service的一个代理对象。而获取serviceManger的代理对象也是这两步,服务端真正的serviceManger实际上使用C实现的,我们平常用的只不过是java层获取到的一个serviceManger的代理对象:

得到ServiceManager的Java层代理对象  sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject()); 

首先BinderInternal.getContextObject()就是获取到serviceManger的BpBinder对象(这是Native层的事了),一般其他的service是需要在serviceManager里注册自己,然后客户端才可以通过serviceManager来获取到BpBinder,然而serviceManager这个服务是不需要注册的,默认给他一个句柄了,就是0,所以通过BinderInternal.getContextObject()直接可以获取到BpBinder,然后通过asInterface(),这样java层就得到了serviceManager的代理对象了,也就是serviceManagerProxy.


   上面已经很详细的讲解了客户端如何获取serviceManager代理对象的过程,有了serviceManager,那么上面说的Binder机制的四个部分就都具备了,接下来说说通过serviceManger,普通的服务端和客户端的通信过程。


   首先服务需要在serviceManager里注册,即需要调用serviceManagerProxy的addService()来注册自己,而addService()里面要传两个参数:1.service的名字 2.service的binder实体,所以addService()里面实际上就是把service名字和binder实体封装在Parcel包里,然后调用transact()来发送这个包到Binder驱动里,Binder驱动接收到这个包后,如果发现这个Binder是新传递来的,那么就会为期在内核空间里创建一个相应的Binder实体节点和一个对该节点的引用,创建完毕后,Binder驱动就会把该引用传递给serverManager,serverManager收到后就会从中取出Binder的名字和引用插入到一张数据表里,也就是映射表。


    接下来就是客户端如何通过serverManager(serverManager在java层的代理对象可以通过封装的方法getIServiceManager()来得到,它里面也就是上面我们说的如何得到serverManager的代理对象来获取服务端的BpBinder对象并转化为service的代理对象的过程了。客户端通过serverManager的getservice(参数为服务名称)就可以获取到相应服务的BpBinder对象了,那么getService()里面做了些上面呢?他里面是通过parcel包的形式把我们的请求通过 transact()的方式传递给Native层的serverManager的BpBinder对象,然后Native层的这个serverManager负责来找到你想要的服务的BpBinder,并给你返回去。这就是客户端获取服务BpBinder的过程,然后只要通过asInterface()就可以转BpBinder为server的代理对象了。


   至此客户端获取到服务端的代理对象,可以很方便的进行通信了。



基本上这就是利用Binder机制进行通信的过程了,当然这边没有对Binder驱动的实现,以及Native层进行更深入的讨论。






下面分享两篇关于Binder我觉得很不错的写的很细致的文章:

http://m.oschina.net/blog/149578

http://www.360doc.cn/article/168576_425018852.html



 以上就是安卓中Binder机制实现进程间通信的过程。









1 0
原创粉丝点击