跨进程通信机制

跨进程通信机制


1. Binder

1.1. Binder定义

Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。Android中的四大组件Activity,Service,Broadcast,ContentProvider,不同的App等都运行在不同的进程中,它是这些进程间通讯的桥梁。

1.2. Binder架构

Binder跨进程通信机制模型基于 模式,其在 framework 层进行了封装,通过 JNI 技术调用 Native(C/C++)层的 Binder 架构。而在Native层,Binder 则以 ioctl 的方式与 Binder 驱动进行通讯,其架构图如下:

跨进程通信机制
  • 首先需要注册端,只有注册了端,端才有通讯的目标,端通过 注册服务,注册的过程就是向 Binder 驱动的全局链表 中插入端的信息( 是结构体,每个 结构体中都有 任务队列),然后向 的 列表中缓存注册的服务;
  • 在端注册完成后,端就可以与其进行通讯了。在通讯之前端需要先获取服务,即拿到服务的代理,也可以理解为引用。获取端的方式就是通过 到 列表中查询所需服务并返回端的代理, 列表就是所有已注册服务的通讯录,保存了所有已注册服务的信息;
  • 在有了端的代理之后即可向端发送请求。端通过 将请求参数发送给 ,通过共享内存的方式使用内核方法 将请求参数先拷贝到内核空间,此时端进入等待状态,然后 Binder 驱动向端的 队列里面插入一条事务,执行完成之后通过 将内核的执行结果拷贝到用户空间(这里只执行拷贝命令,并没有拷贝数据),唤醒等待的客户端并把结果返回,即完成了一次通讯。

1.4. Binder驱动

Linux 内核的运行空间与用户程序的运行空间是相互隔离的,即使用户程序崩溃了,内核也不受影响。内核空间与用户空间的交互过程如下图所示:

跨进程通信机制

对于底层Binder驱动而言,通过 链表记录所有创建的 结构体,Binder 驱动中的每一个 结构体都与用户空间中的一个用 Binder 通信的进程相对应,且每个进程有且只有一个 对象,通过单例模式实现。在每个进程中可以有多个线程,每个线程对应一个 对象, 对象也是通过单例模式实现,在 Binder 驱动层也有与之相对应的结构,即 结构体。在 结构体中通过成员变量 来记录当前进程内所有的 。

每个 Server 进程在启动时创建一个 Binder 线程池,并向其中注册一个 Binder 线程,之后 Server 进程也可以向 binder 线程池注册新的线程。当 Binder 驱动在探测到没有空闲 binder 线程时,也可以主动向 Server 进程池注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 Binder 线程。对于所有 端进程的 Binder 请求都是交由 端进程的 Binder 线程来处理的。

1.6. ServiceManager启动

ServiceManager提供注册服务与查询服务的功能,其启动如下图所示:

跨进程通信机制
  • 注册 服务端,通过 ServiceManager 的 方法来注册服务;
  • 首先 ServiceManager 向 Binder 驱动发送 命令,并携带 命令,同时注册服务的线程进入等待状态 。 Binder 驱动收到请求命令向 ServiceManager 的 队列里面添加一条注册服务的事务。事务的任务就是创建服务端进程 信息并插入到 链表中;
  • 事务处理完之后发送 命令,ServiceManager 收到命令后向 列表中添加已经注册的服务。最后发送 命令唤醒等待的线程,通知注册成功。

1.8. ServiceManager 获取服务

请求服务过程,就是向serviceManager进程查询指定服务,当执行时,会区分请求服务所属进程情况:

  • 当请求服务的进程与服务属于不同进程,则为请求服务所在进程创建对象,指向服务进程中的,即返回请求服务的一个代理对象;
  • 当请求服务的进程与服务属于同一进程,则不再创建新对象,而是返回的对象的真实子类;

ServiceManager 获取服务的流程如下图所示:

跨进程通信机制
  • 服务注册完成;
  • 首先通过 ServiceManager 获取到服务端的 代理对象,通过调用 将参数,方法标识传给 ServiceManager,同时客户端线程进入等待状态;
  • ServiceManager 将用户空间的参数等请求数据复制到内核空间,并向服务端插入一条执行方法的事务。事务执行完通知 ServiceManager 将执行结果从内核空间复制到用户空间,并唤醒等待的线程,响应结果,通讯结束。

2. AIDL

2.1. 概述

AIDL,全称是 “Android Interface Definition Language”,也就是 “Android接口定义语言”。设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。

在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供操作的对象。而通过 AIDL 即可定义客户端与服务端均认可的编程接口,以便二者之间实现通信。

2.2. AID语法

AIDL 的语法基本上和 Java 是一样的,只是在一些细微处有些许差别,差别之处主要如下所示:

  • 文件类型:用 AIDL 书写的文件的后缀是****,而不是 。

  • 数据类型:AIDL 默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的。但是除了这些类型之外的数据类型,在使用之前必须导包,即使目标文件与当前正在编写的 .aidl 文件在同一个包下也是需要导包的(在 Java 中,这种情况是不需要导包的)。

    • 默认支持的数据类型包括:
      • Java中的八种基本数据类型:booleanbytecharshortintfloatdoublelong
      • String 类型;
      • CharSequence 类型;
      • List 类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable,List可以使用泛型;
      • Map 类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable,Map是不支持泛型的。
  • 定向tag:AIDL 中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端; out 表示数据只能由服务端流向客户端;而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个客户端中传入方法的对象的完整数据,但是客户端该对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

    注:Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in

  • 两类AIDL文件:第一类是用来定义 parcelable 对象,以供其他 AIDL 文件使用 AIDL 中非默认支持的数据类型的。第二类是用来定义方法接口,以供系统使用来完成跨进程通信的。两类文件都是在“定义”,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。
    注:所有的非默认支持数据类型必须通过第一类AIDL文件定义 parcelable 对象才能被使用。

举例如下:

目录结构如图所示:

跨进程通信机制

2.3. 如使用AIDL实现跨进程通信

在进行跨进程通信时,AIDL 中定义的方法里包含非默认支持的数据类型与否,要进行的操作是不一样的。如果不包含,则只需要编写一个 文件(第二类),如果包含,那么我们通常需要写 n+1 个 文件( n 为非默认支持的数据类型的种类数)。所以接下来以 AIDL 文件中包含非默认支持的数据类型为例进行介绍。

在构建每个包含文件的应用时,Android SDK 工具会生成基于该 文件的接口,并将其保存到项目的 目录中。服务必须视情况实现 接口。然后,客户端应用便可绑定到该服务,并调用 中的方法来执行 IPC。

使用 AIDL 创建绑定服务的步骤如下:

  • 创建文件:
    • 此文件定义带有方法签名的编程接口。
  • 实现接口:
    • Android SDK 工具基于 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 的内部抽象类,用于扩展 类并实现 AIDL 接口中的方法,必须扩展该类并实现这些方法。
  • 向客户端公开接口:
    • 实现 并重写 ,从而返回 类的实现。

2.3.1. 创建 文件

创建、文件,代码如下:

定义接口服务时注意:

  • 方法可带零个或多个参数,返回值或空值;
  • 所有非默认支持的数据类型的参数均需要指示数据流向的方向标记:、 或 。默认支持的数据类型的参数只能为in;
  • 可以在 ADL 接口中定义 String 常量和 int 字符串常量,如:

2.3.2. 实现 Parcelable 接口

可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。但必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持 接口。

实现 Parcelable 接口相当于 Android 提供的一种自定义序列化机制,不仅要求实现接口中定义的方法,而且要求在实现类中定义一个名为、类型为的静态常量。

  • 创建与、对应的文件、,对应的java文件与aidl文件的包名一致。、中的内容分别如下所示:

来源:OOOOOho.

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年8月21日
下一篇 2021年8月21日

相关推荐