android-----我眼中的Binder

来源:互联网 发布:unity3d 引擎 编辑:程序博客网 时间:2024/05/23 01:57

        Binder作为进程间通信方式(IPC)的一种,算Android中比较难理解的部分了,今天计划以自己所认识的framework层的Binder原理来做个总结,好了,我们开始吧!

        Android中利用Binder通信,首先肯定需要获得Binder对象了,但是系统服务和我们自定义服务Binder对象的获取方式是不一样的,原因就在于系统服务是在系统启动的时候被注册到ServiceManegr的,我们只需要通过ServiceManager.getService(String name)传入想要获得的系统服务的名字就可以获得其对应的Binder对象进行进程间通信了,系统已经将该Service所提供的服务内容全部写好了;但是对于我们自定义的服务想要实现进程间通信的话,Client端和Server端都是我们自己写的,所以Android提供了AIDL来帮你简化这个过程,而Server端的实现一般是借助于Service来完成的,下面我们分别从系统服务和自定义服务两个方面来进行分析;

      先来看系统服务:

        首先从系统刚刚启动说起,init进程会启动一个叫ServiceManager的进程,该进程启动之后会做三件事:(1)通过open打开设备文件/dev/binder,将该文件中的内容通过mmap映射到本进程空间中;(2)通过IO控制命令BINDER_SET_CONTEXT_MGR将当前进程注册到Binder驱动中,Binder驱动便会为他在内核空间创建一个称为binder_context_mgr_node的节点出来,这个节点也就是ServiceManager在内核空间的Binder实体了,并且将他的句柄设置为0,他的创建是在系统启动的时候,这个节点在Binder驱动中是唯一的,所以也就造成了ServiceManager区别于其他Server了,但他仍然是运行在用户空间的;(3)ServiceManager启动之后就会无限循环等待Server和Client之间的进程间通信请求了;接着Zygote进程会孵化出一个子进程SystemService,我们的大部分系统服务比如ActivityManagerService、WindowManagerService、MediaPlayService等都是该进程内的一个线程,这些服务会通过调用ServiceManager.addService方法添加到ServiceManager中的服务列表svclist中,这样我们的系统服务就已经都注册到了ServiceManager里面了;

        上面讲了ServiceManager,他是随着系统的启动而创建的,它不同于普通的Server,甚至可以理解为是普通Server的Server,系统会将系统所要提供的服务注册到它的服务列表里面,服务列表中存储的便是服务名字+服务在Binder驱动中的句柄,这里的句柄可以理解为同一进程内部的引用,只不过现在是跨进程通信换了个名字而已,如果我们的应用想要用到某个系统服务的话,可以通过传入服务名字调用ServiceManager.getService得到该服务对应的Binder对象的方式进行获取,接着通过这个Binder对象进行相应操作即可,具体这就是ContentProvider为我们封装的内容了;

        Binder是通过客户端/服务端模式实现的,要想明白Binder的实现原理,我们需要明白几个概念之间的关系:Server/Client/ServiceManager/Binder驱动,他们四个是Binder架构的核心,尤其是ServiceManager和Binder驱动,因为我们的应用程序安装到系统上的时候,都会被分配一个唯一的UID用户ID,他是运行在独立进程中的,Linux基于用户安全的考虑是不允许我们直接访问系统上服务的,因为他们分属于不用的进程,进程间的数据是不能共享的,我们要想访问这些系统服务就涉及到了进程间通信了,那么势必就需要内核空间的参与了,所以用到了Binder驱动,它有点类似于桥的作用;那么平常我们是怎么做到访问系统服务的呢?比如媒体资源,是通过ContentProvider,他的底层实现就是Binder,只不过被封装了而已;

        我们可以通过一个例子来说明他们四者之间的关系,假如你想问你同学借点钱,那么你现在就是Client端,你同学就是Server端,但是你两是异地的,不能直接见面,怎么办呢,打电话呗,首先你先从通讯录里面找到你同学的电话,这里的通讯录就是ServiceManager了,然后打过去借就好了,那么电信运营商就是Binder驱动了,没有他你两根本联系不起来;

        讲完了所有涉及到的概念之后,就该看看是怎么通信的了,也就是ServiceManager.addService和ServiceManager.getService到底做了些什么事了?

        首先来说说ServiceManager.addService,也就是我们的Server是怎么和Binder驱动通信的:

        ServiceManager.addService(String name, IBinder service):传入的内容是Server的名字和该Server在Binder驱动中对应的对象;

        (1):Server首先将自己作为对象,并且附上一个句柄为0的值(用于访问ServiceManager),将这些内容封装成一个数据包,open有关Binder的设备文件/dev/binder,将封装好的该数据包发送给Binder驱动;

        (2):Binder驱动在收到这个数据包之后,发现里面存在一个Server对象,首先会在Binder驱动自己里面新建该Server对应的Binder实体,并赋予一个大于0的句柄,同时会将该句柄也加入到数据包中,接着将该数据包发送到句柄为0对应的对象上面,也就是ServiceManager上面了;

        (3):ServiceManager收到转发给自己的数据包之后,会查看其服务列表svlist中是否已经存在当前Server名字的服务,不存在的话,会将当前服务+当前服务对应于Binder驱动中的句柄加入到列表中;

        这样,系统服务就注册到ServiceManager中了;

        接下来便是ServiceManager.getService部分了,也就是Client是怎么和Binder驱动进行通信,返回对应请求的Binder对象,也就是该Server在Binder驱动中的句柄的:

        ServiceManager.getService(String name):传入的参数是将要请求的服务的名字

        (1):Client首先会将要获取的服务的名字以及一个句柄为0的值(为了访问ServiceManager)封装成一个数据包,open有关Binder的设备文件/dev/binder,将该数据包发送给Binder驱动;

        (2):Binder驱动在收到数据包之后发现里面有句柄为0的信息,就将该数据包转发给了ServiceManager来进行处理了;

        (3):ServiceManager在收到数据包之后根据服务的名字查看自己的服务列表svclist,找到之后会将其对应的在Binder驱动中的句柄信息也封装成一个数据包;

        (4):该数据包也会通过Binder驱动被发送给Client端;

        (5):Client端在收到数据包之后,就得到了自己所请求的服务在Binder驱动中的句柄,他会利用这个句柄信息在自己本地创建一个远程Server的代理,以后Client发消息都是发给这个代理的,随后的通信便变成了代理通过Binder驱动与真正的Server进行交互了,以此完成跨进程间的通信;

        这样系统服务是怎么注册到ServiceManager里面以及我们怎么获得这些服务对应于Binder驱动的句柄也就是Binder对象的过程讲解就已经结束了,接下来便是我们自定义服务是怎么通过Binder进行进程间通信的呢?

     自定义服务:

        这里就要用到Android为我们自定义进程间通信所提供的AIDL文件了,没有这个文件,你完全也可以实现跨进程通信,但是序列化,反序列化数据的封装都将需要你自己来实现,有了AIDL之后这个过程会显的比较简单,你并不需要关心序列化反序列化的顺序问题,只需要将Server进程想要提供给Client进程访问的方法定义在一个.aidl文件中即可,我们在此将他命名为Ixxx.aidl,那么系统将会为该AIDL文件自动生成对应的Ixxx.java文件;

        简单说说Ixxx.java的类结构,将是下面这样的伪代码形式:

public interface Ixxx extends IInterface{public static abstarct class Stub extends Binder implements Ixxx{public static Ixxx asInterface(Binder binder){}public Binder asBinder(){}public boolean onTransact(int code,Parcel data,Parcel reply,int flags){}private static class Proxy implements Ixxx{public Binder asBinder(){}//Ixxx接口中的一些实现方法,当然这些实现并不是真正的逻辑实现,而只是通过transcat进行的一些进程切换操作}}}

       可以看到Ixxx.java中存在一个Stub静态抽象类和Prxoy静态类,最关键的方法就是Stub类中的asInterface了,他会根据我们传入的Binder对象来判断是跨进程通信还是进程内部通信,如果是进程内部通信,该方法返回的将是Ixxx.Stub对象;如果是跨进程
通信返回的将是Ixxx.Stub.Proxy对象,这个代理对象中将的方法会通过调用transact方法来进行内核态的切换;

        下面我以文字的方式简述下我们自定义服务实现跨进程通信的原理:

        (1):首先,我们需要创建一个AIDL文件,将其命名为Ixxx.aidl,里面定义了服务端进程想要提供给客户端进程的方法列表,系统会为我们生成一个Ixxx.java文件;

        (2):我们的自定义服务端主要是通过service来实现的,在service里面实现具体提供给客户端的方法的操作代码,并且通过onBind方法返回此服务端对应的Binder对象,而后我们的客户端通过bindService绑定服务端的时候就可以获得这个服务端的Binder对象了;

        (3):在客户端获得Binder对象之后会调用Ixxx.Stub的asInterface方法将Binder对象传入,获得Ixxx对象,在这里就将跨进程和通进程通信分开了,如果是跨进程通信的话asInterface返回的是Ixxx.Stub.Proxy代理对象,那么以后客户端调用服务端的方法实际上是首先调用的Ixxx.Stub.Proxy代理对象里面对应于服务端的方法,这个代理对象的方法会通过transact陷入内核态来进行实际上的进程间通信调用服务端的onTransact方法,在onTransact方法中会根据标志调用不同的服务端方法;如果是同进程通信的话,asInterface返回的是Ixxx.Stub对象,则直接调用服务端方法,没有必要陷入内核态来执行了;

        这也便是我们自定义服务实现进程间通信的简单过程了;

        在使用AIDL实现Binder通信的过程中,我们应该注意一点的就是,AIDL中不管是服务端方法还是客户端方法都是运行在各自的Binder线程池中的,如果我们想要更新UI的话,需要用到Handler进行切换操作;

       

1 0
原创粉丝点击