Binder机制总结

在android中进行跨进程通信通常有以下几种方式:

  1. 使用Intent
  2. 使用文件共享
  3. 使用Messenger
  4. 使用AIDL
  5. 使用ContentProvider

其中Intent中可以携带Bundle,而Bundle实现了Parcelable接口,所以可以在不同的进程间进行传输。文件共享就是读写文件,比如常用的SharedPreferences就是以XML来存储的文件。而Messenger,AIDL,ContentProvider都是基于Binder的,所以学习并理解Binder就尤为重要。

虽然Android继承使用Linux的内核,但Linux与Android的通信机制不同。

在Linux中使用的IPC通信机制如下:

传统IPC:无名pipe, signal, trace, 有名管道

AT&T Unix 系统V:共享内存,信号灯,消息队列

BSD Unix:Socket

而在Android中,并没有使用这些,取而代之的是Binder机制。Binder机制是采用OpenBinder演化而来,在Android中使用它的原因如下:

采用C/S的通信模式。而在linux通信机制中,目前只有socket支持C/S的通信模式,但socket有其劣势,具体参看第二条。

有更好的传输性能。对比于Linux的通信机制,

socket:是一个通用接口,导致其传输效率低,开销大;

管道和消息队列:因为采用存储转发方式,所以至少需要拷贝2次数据,效率低;

共享内存:虽然在传输时没有拷贝数据,但其控制机制复杂(比如跨进程通信时,需获取对方进程的pid,得多种机制协同操作)。

安全性更高。Linux的IPC机制在本身的实现中,并没有安全措施,得依赖上层协议来进行安全控制。而Binder机制的 UID/PID是由Binder机制本身在内核空间添加身份标识,安全性高;并且Binder可以建立私有通道,这是linux的通信机制所无法实现的 (Linux访问的接入点是开放的)。

那什么是Binder呢?

  1.     从IPC角度来说,Binder是Android中一种跨进程通信的方式。
  2.     从Android FrameWork角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。
  3.     从Android应用层的角度来说, BInder是客户端和服务端进行通信的媒介。

Binder通信机制流程(整体流程)

首先server会通过Binder向ServiceManager注册自己的服务,Client想要使用远程服务时会通过Binder去ServiceManager申请服务,申请成功之后便可以取得和Server的联系了,可以调用Server端的方法。

这里面一共有四个角色,分别为Client,Server,Binder,ServiceManager

  1. Client:客户端,服务的使用者
  2. Server:服务端,服务的提供者
  3. Binder:月老,牵线的
  4. ServiceManager:通讯录,记录每个Server的联系方式

需要注意的一点是,Client通过Binder来跨进程取得Server的联系方式,但Client与ServerMangaer的通信,Server与ServerManager的通讯也是跨进程,这个问题就像买匡威鞋子一样

哈哈哈,这个问题就要从系统刚刚启动说起了,init进程会启动一个叫ServiceManager的进程,该进程启动之后会做三件事:

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

server向ServiceManager注册服务流程图

Service向ServerManager中注册自己会调用Service.addService(String name,IBinder service),传入的内容是 Server的名字和该Server驱动中对应的对象;

  1. Server首先将自己作为对象,并且附上一个句柄为0的值(用于访问ServiceManager),将这些内容封装成一个数据包,open有关Binder的设备文件/dev/binder,将封装好的该数据包发送给Binder驱动;
  2. Binder驱动在收到这个数据包之后,发现里面存在一个Server对象,首先会在Binder驱动自己里面新建该Server对应的 Binder实体,并赋予一个大于0的句柄,同时会将该句柄也加入到数据包中,接着将该数据包发送到句柄为0对应的对象上面,也就是 ServiceManager上面了;
  3. ServiceManager收到转发给自己的数据包之后,会查看其服务列表svlist中是否已经存在当前Server名字的服务,不存在的话,会将当前服务+当前服务对应于Binder驱动中的句柄加入到列表中;

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

Client向ServiceManager获取服务流程图

Client和Binder驱动通信的时候会调用ServiceManager.getService(String name)方法,传入的参数就是要请求的服务的名字,返回对应请求的Binder。

  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线程池中的,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。并且如果我们想要更新UI的话,还需要用到Handler进行切换操作;

匿名Binder

之前在介绍Android使用Binder机制的优点中,提到Binder可以建立点对点的私有通道,匿名Binder就是这种方式。在 Binder通信中,并不是所有用来通信的Binder实体都需要注册给SM广而告之的,Server可以通过已建立的实体Binder连接将创建的 Binder实体传给Client。而这个Binder没有向SM注册名字。这样Server与Client的通信就有很高的隐私性和安全性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章