android 跨进程通信的代表Binder

android 跨进程通信的代表Binder

  • Binder为什么能跨进程通信?
  • android为什么选择binder而不是Socket

再开始本文之前,这两个问题你可以先思考下,这两个问题在Binder及其重要

前言

想想如果在同一进程,你想访问对方内部的函数变量是很简单的。

但是一旦不在一个进程,如Application1进程去访问AMS进程的东西,你就无法”直接”访问,你需要一个东西帮你转接一下。

android 跨进程通信的代表Binder

android为什么选择binder而不是Socket

linux支持的IPC:

  • 传统的管道
  • System V IPC(消息队列/共享内存/信号量)
  • socket

可靠性:

需要在各个服务端架设一套协议来实现Client-Server通信但这样增加了系统的复杂性,在手机这种条件复杂,资源稀缺的环境下可靠性也难以保证。

传输性能:

Socket
传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信(IM应用)

消息队列管道
采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,
至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。

IPC 数据拷贝次数
Socket/管道/消息队列 2
共享内存 0
Binder 1

安全性

传统IPC没有任何,安全措施,完全依赖上层协议来确保

主要是传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份

基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder

总结:

看来无论是可靠性还是传输性能,安全性binder是完胜

Binder介绍

Binder机制的模块

Binder驱动 -路由器
Service Manager -DNS
Binder Client-客户端
Binder Service-服务端

正常流程呢 客户端->路由器->dns->路由器->服务端

android 跨进程通信的代表Binder

运输载体 Parcel

Parcel具有打包和重组能力,为什么会牵扯到打包呢

因为不同进程传递的不再是引用而是原始数据,你给他引用,它帮你把引用指向的数据都打包起来运输过去了。

打包的数据类型没有啥限制,基本数据类型,实体类,数组,都可以,但是有的数据类型还是需要序列化的。

写个例子来玩玩AIDL

首先创建两个工程aidlclient(客户端),aidlserve(服务端)

0.服务端创建AIDL(IMyAidlInterface.aidl)

package com.feitianzhu.aidlserve;
interface IMyAidlInterface {

    int add(int num1,int num2);
}

1.建立一个远程服务(IRemoteService)

public class IRemoteService extends Service {
  /**
   * 将接口暴露给客户端
   * @param intent
   * @return
   */
  @Nullable @Override public IBinder onBind(Intent intent) {
    return iBinder;
  }

  private IBinder iBinder = new IMyAidlInterface.Stub() {
    @Override public int add(int num1, int num2) throws RemoteException {
      return num1 + num2;
    }
  };
}

看到onBind方法中,将接口暴露给给客户端,同时你会发现IBinder,

IBinder是啥?

他说接口,Binder是他的实现类

3.注册服务,并在activity中启动

    
    

 startService(new Intent(this,IRemoteService.class));

ok,运行服务端到手机中,你就服务起来了,接着我们开始客户端跨进程与服务通信了

4.将服务端的AIDL文件复制到客户端去

5.准绑定服务传值调用方法吧

  //绑定服务
  private void bindService(){
    Intent intent = new Intent();
    intent.setComponent(new ComponentName("com.feitianzhu.aidlserve",
        "com.feitianzhu.aidlserve.IRemoteService"));
    bindService(intent,conn, Context.BIND_AUTO_CREATE);
  }

大家应该很好奇第二个参数是干什么?

连接后回调,断开后回调,总结就是回调

  //绑定服务回调
  private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
      //服务绑定成功后调用,获取服务端的接口,这里的service就是服务端onBind返
      //回的iBinder即已实现的接口
      iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
      //解除绑定时调用,清空接口,防止内容溢出
      iMyAidlInterface = null;
    }
  };

顺利拿到IMyAidlInterface,有了他你就可以通信了,里面有我们在服务端写好的add方法,
我们调用下

mTotal = iMyAidlInterface.add(mNum1,mNum2);

顺利调用add方法得到想要的值

回想上面的代码思路我重新给你们说说这四个组成部门

Server进程:

提供服务的进程

Client进程:

使用服务的进程

ServiceManager进程:

其实它存在感比较低,带有一个Manager,也就是他是服务管理者

这不是我猜的,是事实,他还管理者很多服务

还记得他有DNS的称号么,当你告诉他你要哪个服务的时候,他会给你一个服务代表引用,如上文中的bindservice()

iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);

Binder驱动:

驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

如果还想深入了解的话,请往下看….其实写到这里大家应该会用AIDL了,但是binder机制大家还是不明白,是吧

如果是的话。。。

我们聊点干活,请往下看

0.Binder在Service服务中的作用

android 跨进程通信的代表Binder

Server注册服务。Server作为众多Service的拥有者,当它想向Client提供服务时,得先去Service Manager(以后缩写成SM)那儿注册自己的服务。Server可以向SM注册一个或多个服务。

Client申请服务。Client作为Service的使用者,当它想使用服务时,得向SM申请自己所需要的服务。Client可以申请一个或多个服务。

当Client申请服务成功后,Client就可以使用服务了。
SM一方面管理Server所提供的服务,同时又响应Client的请求并为之分配相应的服务。扮演的角色相当于月老,两边牵线。这种通信方式的好处是: 一方面,service和Client请求便于管理,另一方面在应用程序开发时,只需为Client建立到Server的连接,就可花很少时间和精力去实 现Server相应功能。那么,Binder与这个通信模式有什么关系呢?!其实,3者的通信方式就是Binder机制(例如:Server向SM注册服 务,使用Binder通信;Client申请请求,用的是Binder通讯)

1.Binder通信机制流程

android 跨进程通信的代表Binder

上图即是Binder的通信模型。我们可以发现:
SM(serviceManager)

Client和Server是存在于用户空间

Client与Server通信的实现,是由Binder驱动在内核空间实现

SM作为守护进程,处理客户端请求,管理所有服务项。
为了方便理解,我们可以把SM理解成DNS服务器; 那么Binder Driver 就相当于路由的功能。

2.Server向SM注册服务

android 跨进程通信的代表Binder

首先,XXXServer(XXX代表某个)在自己的进程中向Binder驱动申请创建一个XXXService的Binder的实体,

Binder驱动为这个XXXService创建位于内核中的Binder实体节点以及Binder的引用,注意,是将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个名叫XXX的Service。

SM收到数据包后,从中取出XXXService名字和引用,填入一张查找表中。

此时,如果有Client向SM发送申请服务XXXService的请求,那么SM就可以在查找表中找到该Service的Binder引用,并把Binder引用(XXXBpBinder)返回给Client。
在进一步了解Binder通信机制之前,我们先弄清几个概念。

引用和实体。这里,对于一个用于通信的实体(可以理解成具有真实空间的Object),可以有多个该实体的引用(没有真实空间,可以理解成实体的 一个链接,操作引用就会操作对应链接上的实体)。如果一个进程持有某个实体,其他进程也想操作该实体,最高效的做法是去获得该实体的引用,再去操作这个引 用。

有些资料把实体称为本地对象,引用成为远程对象。可以这么理解:引用是从本地进程发送给其他进程来操作实体之用,所以有本地和远程对象之名。

总结:前文中说服务创建,需要去服务总管(serviceManager)那里去注册下,通过太监(binder驱动)去把东西传递给总管(名字和对象引用),服务总管就在簿册中记录下,如果有人要来拿东西了,就把引用给他,但是实体东西始终是在共享空间中,使用者操纵引用就像游戏中你只是操纵游戏中任务,游戏给你反馈,但是你并不是游戏中的任务

3.如何获得SM的远程接口

android 跨进程通信的代表Binder

如果你足够细心,会发现这里有一个问题:

Sm和Server都是进程,Server向SM注册Binder需要进程间通信,当前实现的是进程间通信却又用到进程间通信。这就好比鸡生蛋、蛋生鸡,但至少得先有其中之一。

巧妙的Binder解决思路:

针对Binder的通信机制,Server端拥有的是Binder的实体;Client端拥有的是Binder的引用。
如果把SM看作Server端,让它在Binder驱动一运行起来时就有自己的Binder实体(代码中设置ServiceManager的Binder 其handle值恒为0)。这个Binder实体没有名字也不需要注册,所有的client都认为handle值为0的binder引用是用来与SM通信 的(代码中是这么实现的),那么这个问题就解决了。那么,Client和Server中这么达成协议了(handle值为0的引用是专门与SM通信之用 的),还不行,还需要让SM有handle值为0的实体才算大功告成。怎么实现的呢?!当一个进程调用Binder驱动时,使用 BINDER_SET_CONTEXT_MGR命令(在驱动的binder_ioctl中)将自己注册成SM时,Binder驱动会自动为它创建 Binder实体。这个Binder的引用对所有的Client都为0。

总结:SM在binder驱动一启动的时候就拥有了自己的实体,而它专门用来与SM通信用的

4.Client从SM获得Service的远程接口

android 跨进程通信的代表Binder

Server向SM注册了Binder实体及其名字后,Client就可以通过Service的名字在SM的查找表中获得该Binder的引用了 (BpBinder)。Client也利用保留的handle值为0的引用向SM请求访问某个Service:我申请访问XXXService的引用。 SM就会从请求数据包中获得XXXService的名字,在查找表中找到该名字对应的条目,取出Binder的引用打包回复给client。之 后,Client就可以利用XXXService的引用使用XXXService的服务了。
如果有更多的Client请求该Service,系统中就会有更多的Client获得这个引用。

5.建立C/S通路后

android 跨进程通信的代表Binder

首先要理清一个概念:client拥有自己Binder的实体,以及Server的Binder的引用;Server拥有自己Binder的实体,以及Client的Binder的引用。我们也可以从接收方和发送方的方式来理解:

从client向Server发数据:Client为发送方,拥有Binder的实体;Server为接收方,拥有Binder的引用

从server向client发数据:Server为发送方,拥有Binder的实体;client为接收方,拥有Binder的引用。
也就是说,我们在建立了C/S通路后,无需考虑谁是Client谁是Server,只要理清谁是发送方谁是接收方,就能知道Binder的实体和引用在哪边。

建立CS通路后的流程:(当接收方获得Binder的实体,发送方获得Binder的引用后)

发送方会通过Binder实体请求发送操作。

Binder驱动会处理这个操作请求,把发送方的数据放入写缓存(binder_write_read.write_buffer) (对于接收方为读缓冲区),并把read_size(接收方读数据)置为数据大小(对于具体的实现后面会介绍);

接收方之前一直在阻塞状态中,当写缓存中有数据,则会读取数据,执行命令操作

接收方执行完后,会把返回结果同样用binder_transaction_data结构体封装,写入写缓冲区(对于发送方,为读缓冲区)

参考:彻底了解Binder机制原理和底层实现