Android 跨进程通信aidl及binder机制详解(一)

前言

上文中描述了,什么是绑定服务、以及创建一个绑定服务都可以通过哪些方式,同时说了通过扩展Binder类来创建一个绑定服务,并使用一个例子来说明了客户端与服务端的绑定过程,最后又总结了绑定服务的生命周期与调用过程。由于上一篇文章都是在本地应用(单进程)下进行实战的,所以本节主要讲解跨进程通信的主要方式和开发步骤。

创建绑定服务跨进程通信

对于Android 跨进程通信,Android官方提供了两种方式,一种是通过使用 Messenger,一种是使用AIDL。下面摘自官网描述

  • 使用 Messenger
    如需让接口跨不同进程工作,您可以使用 Messenger 为服务创建接口。采用这种方式时,服务会定义一个 Handler,用于响应不同类型的 Message 对象。此 Handler 是 Messenger 的基础,后者随后可与客户端分享一个 IBinder,以便客户端能利用 Message 对象向服务发送命令。此外,客户端还可定义一个自有 Messenger,以便服务回传消息。
    这是执行进程间通信 (IPC) 最为简单的方式,因为 Messenger 会在单个线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。

  • 使用 AIDL
    Android 接口定义语言 (AIDL) 会将对象分解成原语,操作系统可通过识别这些原语并将其编组到各进程中来执行 IPC。以前采用 Messenger 的方式实际上是以 AIDL 作为其底层结构。如上所述,Messenger 会在单个线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,可以直接使用 AIDL。在此情况下,您的服务必须达到线程安全的要求,并且能够进行多线程处理。
    如需直接使用 AIDL,您必须创建用于定义编程接口的 .aidl 文件。Android SDK 工具会利用该文件生成实现接口和处理 IPC 的抽象类,您随后可在服务内对该类进行扩展。

注意:大多数应用不应使用 AIDL 来创建绑定服务,因为它可能需要多线程处理能力,并可能导致更为复杂的实现。因此,AIDL 并不适合大多数应用,本文也不会阐述如何将其用于您的服务。如果您确定自己需要直接使用 AIDL,请参阅 AIDL 文档。

使用Messenger

如果您需要让服务与远程进程通信,则可使用 Messenger 为您的服务提供接口。借助此方式,您无需使用 AIDL 便可执行进程间通信 (IPC)。

为接口使用 Messenger 比使用 AIDL 更简单,因为 Messenger 会将所有服务调用加入队列。纯 AIDL 接口会同时向服务发送多个请求,那么服务就必须执行多线程处理。

对于大多数应用,服务无需执行多线程处理,因此使用 Messenger 可让服务一次处理一个调用。如果您的服务必须执行多线程处理,请使用 AIDL 来定义接口。

以下是对 Messenger 使用方式的总结:

  1. 服务实现一个 Handler,由其接收来自客户端的每个调用的回调。
  2. 服务使用 Handler 来创建 Messenger 对象(该对象是对 Handler 的引用)。
  3. Messenger 创建一个 IBinder,服务通过 onBind() 将其返回给客户端。
  4. 客户端使用 IBinder 将 Messenger(它引用服务的 Handler)实例化,然后再用其将 Message 对象发送给服务。
  5. 服务在其 Handler 中(具体而言,是在 handleMessage() 方法中)接收每个 Message。

这样,客户端便没有调用服务的方法。相反,客户端会传递服务在其 Handler 中接收的消息(Message 对象)。
Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。使用Messenger的例子我们这里就不去验证了,下面我们重点介绍AIDL。

AIDl

AIDL全称Android接口定义语言,利用它可以定义客户端与服务端均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,为了在两个不同进程中的内存之间相互进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 处理此问题。

AIDL可以看做是binder跨进程通信机制在Java层的一种实现方式,在AIDL中定义接口后,由Android sdk 构建之后便可生成Binder的派生类,所以AIDL只是一种工具,为了方便开发而生,所以底层的通信还是基于binder机制,在使用AIDL之前,我们首先来简单熟悉一下Binder与IBinder 。具体的底层原理,我们可在其他文章中体现。

注意:只有在需要不同应用的客户端通过 IPC 方式访问服务,并且希望在服务中进行多线程处理时,您才有必要使用 AIDL。如果您无需跨不同应用执行并发 IPC,则应通过实现 Binder 来创建接口;或者,如果您想执行 IPC,但不需要处理多线程,请使用 Messenger 来实现接口。无论如何,在实现 AIDL 之前,请您务必理解绑定服务。

java中Binder类

远程对象的基类,IBinder定义的轻量级远程过程调用机制的核心部分。该类是IBinder的一个实现,它提供了此类对象的标准本地实现。

我们通常不会直接实现这个类,而是使用aidl工具来描述所需的接口,让它生成适当的Binder子类。 但是,您可以直接从Binder派生来实现您自己的自定义RPC协议,或者简单地直接实例化一个原始Binder对象以用作跨进程共享的令牌。

这个类只是一个基本的IPC原语;它对应用程序的生命周期没有影响,只有在创建它的进程继续运行时才有效。为了正确地使用它,你必须在顶级应用程序组件(Service、Activity或ContentProvider)的上下文中这样做,让系统知道你的进程应该继续运行。

有时候应用进程会被杀掉或消失,此时需要稍后重新创建一个新的Binder,并在进程再次启动时重新附加它。例如,如果你在一个Activity中使用这个,你的Activity进程可能会在Activity没有启动的时候被杀死;如果活动稍后重新创建,您将需要创建一个新的Binder,并再次将其交还到正确的位置;您需要意识到您的进程可能因为其他原因(例如接收广播)而启动,这将不涉及重新创建活动,因此运行其代码来创建一个新的Binder。

java中IBinder 接口

远程对象的基本接口,轻量级远程过程调用机制的核心部分,用于执行进程内和跨进程调用时的高性能。此接口描述了与远程对象交互的抽象协议。不要直接实现这个接口,而是从Binder扩展。

IBinder API的关键是由Binder.onTransact()匹配的transact()。这些方法允许您分别向IBinder对象发送调用和接收进入Binder对象的调用。该事务API是同步的,因此对transaction()的调用直到目标从Binder.onTransact()返回才返回;这是调用本地进程中存在的对象时的预期行为,底层进程间通信(IPC)机制确保在跨进程时应用这些相同的语义。

通过transact()发送的数据是一个Parcel,它是一个通用的数据缓冲区,还维护一些关于其内容的元数据。元数据用于管理缓冲区中的IBinder对象引用,以便在缓冲区跨进程移动时维护这些引用。该机制确保当一个IBinder被写入一个Parcel并发送给另一个进程时,如果另一个进程将对同一个IBinder的引用发送回原始进程,那么原始进程将接收到相同的IBinder对象。这些语义允许将IBinder/Binder对象用作跨进程管理的唯一标识(作为令牌或用于其他目的)。

系统在其运行的每个进程中维护一个事务线程池。这些线程用于分派来自其他进程的所有ipc。例如,当一个IPC从进程A执行到进程B时,A中的调用线程在将事务发送到进程B时阻塞在transact()中。B中的下一个可用池线程接收传入的事务,在目标对象上调用Binder.onTransact(),并返回结果Parcel。在收到结果后,进程A中的线程返回以允许其继续执行。实际上,其他进程似乎使用了您没有在自己的进程中创建并执行的额外线程。

Binder系统还支持跨进程递归。例如,如果进程A向进程B执行了一个事务,而进程B在处理该事务时调用了在A中实现的IBinder上的transact(),那么当前在A中等待原始事务完成的线程将负责在B调用的对象上调用binder . ontransact()。这确保了调用远程绑定器对象时的递归语义与调用本地对象时相同。

在使用远程对象时,您通常希望发现它们何时不再有效。有三种方法可以确定这一点:

如果你试图在一个进程不再存在的IBinder上调用transact()方法,它会抛出一个RemoteException异常。
pingBinder()方法可以被调用,如果远程进程不再存在,它将返回false。
linkToDeath()方法可用于向IBinder注册一个DeathRecipient,当包含它的进程消失时将调用IBinder。

Native层binder 驱动程序

Binder
核心有两个:IPC 、RPC
1) 是一种跨进程通信手段(IPC,Inter-Process Communication)。
2) 是一种远程过程调用手段(RPC,Remote Procedure Call)。
从实现的角度来说,Binder的核心被实现成了一个Linux驱动程序,并运行于内核态。这样它才能具有强大的跨进程访问能力。

如果有两个进程A和B ,A要把内容发给B 那就需要用到IPC,ipc可以看作是一个通道。
RPC 远程过程调用,其实就说调用远程提供的方法,也称为远程函数调用。是在A进程这边创建一些B进程远程调用的接口放到本地,在A进程中封装好数据,通过IPC通道,将数据发送给B,从而在B进程接收数据并调用B的接口完成操作。
IPC是基础,它负责数据传输。要想实现IPC,它需要3个要素,源、目的、数据。其中源指的是进程A,目的指的是进程B。当从A进程发送数据到B时,A并不知道该向哪个进程发送数据,于是,B进程将服务注册到服务管理中心上,接着A进程就向服务管理中心查询需要调用的服务,同时会得到一个handle。后续都通过handle进行通信
而上述的执行过程,都需要借助binder驱动程序来进行通信。
在这里插入图片描述
在这里插入图片描述

binder机制架构图

在这里插入图片描述
在这里插入图片描述

binder机制涉及的各个层的类库

在这里插入图片描述

使用AIDL

上述贴了几张图让大家大概的了解到了,跨进程通信的复杂性,两个进程之间的通信不仅涉及到了RPC的远程过程调用,又涉及到了用户和内核之间的数据交互,从上层java层到JNI层又到native层可谓是层层调用。而我们现在不需要了解的那么深入,只要知道跨进程通信很复杂就足够了,要想搞清楚binder原理,建议大家直接从binder驱动代码入手直接看,省时省力,光靠理论可是不能完全理解透彻的。但是binder驱动代码可不是谁都能看懂的,首先要理解binder的设计原理,再去从代码层面才能看懂。

关于跨进程通信,Android层面上提供了aidl工具,方便开发者开发接口,通过应用层调用framework层来完成通信。您必须在 .aidl 文件中使用 Java 编程语言的语法定义 AIDL 接口,然后将其保存至应用的源代码(在 src/ 目录中)内,这类应用会托管服务或与服务进行绑定。在构建每个包含 .aidl 文件的应用时,Android SDK 工具会生成基于该 .aidl 文件的 IBinder 接口,并将其保存到项目的 gen/ 目录中。服务必须视情况实现 IBinder 接口。然后,客户端应用便可绑定到该服务,并调用 IBinder 中的方法来执行 IPC

在腾讯shadow(插件化框架)框架中,主应用与插件应用之间的交互就涉及到了跨进程调用,而且存在主应用与多个插件之间的进程通信,涉及多线程处理。所以此处便用到了aidl生成的代码。下面开始我们看看如何创建一个客户端与服务器,并完成两者之间的交互。

创建aidl绑定服务完成跨进程通信

如要使用 AIDL 创建绑定服务,请执行以下步骤:

  • 创建 .aidl 文件
    此文件定义带有方法签名的编程接口。此文件定义了客户端和服务端的接口。AIDL 使用一种简单语法,允许您通过一个或多个方法(可接收参数和返回值)来声明接口。参数和返回值可为任意类型,甚至是 AIDL 生成的其他接口。
    您必须使用 Java 编程语言构建 .aidl 文件。每个 .aidl 文件均须定义单个接口,并且只需要接口声明和方法签名。

  • 实现接口
    Android SDK 工具会基于您的 .aidl 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现这些方法。

  • 向客户端公开接口
    实现 Service 并重写 onBind(),从而返回 Stub 类的实现。

创建aidl文件

在创建aidl文件之前,我们先来了解一下aidl文件的语法:

说是语法应该叫规则更合适,上文中说到,它就是为生成binder派生类而出现的工具文件,Android编译器会根据aidl文件生成java文件,那么这个过程一定会给aidl文件的编写定义一些规范和约束,来指导开发者按照此规约来定义此文件。

同java语言类似,它包括了自己的数据类型,数据类型有两种,一种是基本的数据类型、集合类的数据类型、此外还包括自定义的数据类型如对象形式的。在java中我们定义的基本数据类型如string ,它本身就实现了序列化与反序列化,基本的java类要想实现序列化与反序列化就需要实现serilizable接口。在aidl文件中也是如此。

默认情况下,支持的类型如下

  • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等)

  • String

  • CharSequence

  • List
    List 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。您可选择将 List 用作“泛型”类(例如,List)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList。

  • Map
    Map 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap。

除了上述类型外还有非默认的数据类型,即实现了Parcelable接口的数据类型。 在跨进程通信情况下,由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域,所以我们不能像平时那样,传一个句柄过去就完事了——句柄指向的是一个内存区域,现在目标进程根本不能访问源进程的内存,所以我们必须将要传输的数据转化为能够在内存之间流通的形式。这个转化的过程就叫做序列化与反序列化。在传递数据之前,客户端需要对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,从而还原其中包含的数据——通过这种方式,我们就达到了在一个进程中访问另一个进程的数据的目的。在Android中,我们通过AIDL进行跨进程通信的时候,选择的序列化方式是实现 Parcelable 接口。

接口定义

定义服务接口时,请注意:

  • 方法可带零个或多个参数,返回值或空值。
  • 所有非原语参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout(见下方示例)。原语默认为 in,不能是其他方向。
    此标记表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

注意:您应将方向限定为真正需要的方向,因为编组参数的开销较大。

  • 生成的 IBinder 接口内包含 .aidl 文件中的所有代码注释(import 和 package 语句之前的注释除外)。
  • 您可以在 ADL 接口中定义 String 常量和 int 字符串常量。例如:const int VERSION = 1;。
  • 方法调用由 transact() 代码分派,该代码通常基于接口中的方法索引。由于这会增加版本控制的难度,因此您可以向方法手动配置事务代码:void method() = 10;。
  • 使用 @nullable 注释可空参数或返回类型。

1、按照以上步骤,我们来创建一个aidl:在Android studio中如下图所示:
在这里插入图片描述
一步操作,一个aidl文件就已经创建了。创建完成后会自动创建一个aidl文件夹,并生成一个aidl文件如下图:
在这里插入图片描述

2、在aidl文件中定义两个测试接口,
// 定义方法,此方法想从服务端拿文件字符串
String getFile();
//发送数据,此方法想向服务端发送消息,并需要服务端返回消息
String sendData(String message);**

aidl文件如下:
// IMyAidlInterface.aidl
package com.cop.ronghw.study_android_exact;// Declare any non-default types here with import statementsinterface IMyAidlInterface {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);// 定义方法String getFile();//发送数据String sendData(String message);
}

3、构建应用:构建项目,Android SDK 工具会生成以 .aidl 文件命名的 .java 接口文件。如下图所示:
在这里插入图片描述

分析由aidl文件生成的同名java文件

上面我们已经通过aidl文件生成了java文件,这个java文件怎么使用呢?首先我们先来分析一下此java文件。由于生成的java文件代码比较长,这里我们来分段解析:首先贴一下代码:

/** This file is auto-generated.  DO NOT MODIFY.*/
package com.cop.ronghw.study_android_exact;
// Declare any non-default types here with import statementspublic interface IMyAidlInterface extends android.os.IInterface {/*** Default implementation for IMyAidlInterface.*/public static class Default implements com.cop.ronghw.study_android_exact.IMyAidlInterface {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/@Overridepublic void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {}// 定义方法@Overridepublic java.lang.String getFile() throws android.os.RemoteException {return null;}//发送数据@Overridepublic java.lang.String sendData(java.lang.String message) throws android.os.RemoteException {return null;}@Overridepublic android.os.IBinder asBinder() {return null;}}/*** Local-side IPC implementation stub class.*/public static abstract class Stub extends android.os.Binder implements com.cop.ronghw.study_android_exact.IMyAidlInterface {private static final java.lang.String DESCRIPTOR = "com.cop.ronghw.study_android_exact.IMyAidlInterface";/*** Construct the stub at attach it to the interface.*/public Stub() {this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.cop.ronghw.study_android_exact.IMyAidlInterface interface,* generating a proxy if needed.*/public static com.cop.ronghw.study_android_exact.IMyAidlInterface asInterface(android.os.IBinder obj) {if ((obj == null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin != null) && (iin instanceof com.cop.ronghw.study_android_exact.IMyAidlInterface))) {return ((com.cop.ronghw.study_android_exact.IMyAidlInterface) iin);}return new com.cop.ronghw.study_android_exact.IMyAidlInterface.Stub.Proxy(obj);}@Overridepublic android.os.IBinder asBinder() {return this;}@Overridepublic boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {java.lang.String descriptor = DESCRIPTOR;switch (code) {case INTERFACE_TRANSACTION: {reply.writeString(descriptor);return true;}case TRANSACTION_basicTypes: {data.enforceInterface(descriptor);int _arg0;_arg0 = data.readInt();long _arg1;_arg1 = data.readLong();boolean _arg2;_arg2 = (0 != data.readInt());float _arg3;_arg3 = data.readFloat();double _arg4;_arg4 = data.readDouble();java.lang.String _arg5;_arg5 = data.readString();this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);reply.writeNoException();return true;}case TRANSACTION_getFile: {data.enforceInterface(descriptor);java.lang.String _result = this.getFile();reply.writeNoException();reply.writeString(_result);return true;}case TRANSACTION_sendData: {data.enforceInterface(descriptor);java.lang.String _arg0;_arg0 = data.readString();java.lang.String _result = this.sendData(_arg0);reply.writeNoException();reply.writeString(_result);return true;}default: {return super.onTransact(code, data, reply, flags);}}}private static class Proxy implements com.cop.ronghw.study_android_exact.IMyAidlInterface {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/@Overridepublic void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeInt(anInt);_data.writeLong(aLong);_data.writeInt(((aBoolean) ? (1) : (0)));_data.writeFloat(aFloat);_data.writeDouble(aDouble);_data.writeString(aString);boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);return;}_reply.readException();} finally {_reply.recycle();_data.recycle();}}// 定义方法@Overridepublic java.lang.String getFile() throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.lang.String _result;try {_data.writeInterfaceToken(DESCRIPTOR);boolean _status = mRemote.transact(Stub.TRANSACTION_getFile, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().getFile();}_reply.readException();_result = _reply.readString();} finally {_reply.recycle();_data.recycle();}return _result;}//发送数据@Overridepublic java.lang.String sendData(java.lang.String message) throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.lang.String _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(message);boolean _status = mRemote.transact(Stub.TRANSACTION_sendData, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().sendData(message);}_reply.readException();_result = _reply.readString();} finally {_reply.recycle();_data.recycle();}return _result;}public static com.cop.ronghw.study_android_exact.IMyAidlInterface sDefaultImpl;}static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_getFile = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);static final int TRANSACTION_sendData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);public static boolean setDefaultImpl(com.cop.ronghw.study_android_exact.IMyAidlInterface impl) {// Only one user of this interface can use this function// at a time. This is a heuristic to detect if two different// users in the same process use this function.if (Stub.Proxy.sDefaultImpl != null) {throw new IllegalStateException("setDefaultImpl() called twice");}if (impl != null) {Stub.Proxy.sDefaultImpl = impl;return true;}return false;}public static com.cop.ronghw.study_android_exact.IMyAidlInterface getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}}/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;// 定义方法public java.lang.String getFile() throws android.os.RemoteException;//发送数据public java.lang.String sendData(java.lang.String message) throws android.os.RemoteException;
}

上述代码看起来比较多,我们把代码收缩贴图看下:
在这里插入图片描述
上图中,包括了我们在创建aidl文件时自定义的接口方法,还包括了两个静态内部类。

两个方法就是我们在定义aild文件的时候自定义的。静态内部类中的 Default类可先忽略,重点在于抽象类Stub。

在文章前面我们说了,跨进程通信,必然有一方作为客户端,一方作为服务端,两端交互就必然规定了一些交互协议,就类似我们熟悉的webservice,webservice的协议使用soap+http,客户端和服务端通过定义命名空间和接口方法来保证两端的通信,因为webservice同样也属于RPC的调用。同理,Android中使用的协议上层使用的是IBinder定义的协议规范,IBinder接口也遵循了底层binder驱动定义的系统调用接口规范,同样也属于RPC调用,也会经过一系列的序列化与反序列的过程。理解了这些,对我们分析这个生成的java类就大有帮助了。

所以,刚刚我们定义的方法 (sendData 和 getFile) 就是用来客户端与服务端交互的方法,即客户端调用了sendData方法,就会封装数据,通过binder驱动调用到了服务端的sendData方法。所以我们同样需要在服务端实现这两个方法,在服务端这两个方法就是涉及了实际的业务逻辑。同样的在客户端也会有这两个方法,客户端这两个方法的作用就是远程调用中需要传哪些参数,构造哪些数据会打个包给服务端。然而看似简单的调用过程,中间过程却有一系列的操作,毕竟要做到两个不同的内存之间相互访问不是简单的事情。Android 提供了binder机制就是做这个事情。

总结:在aidl生成的java文件中提供了客户端和服务端的标准,提供了客户端和服务端的交互机制

Stub:紧接上述的Stub类继续分析,Stub类扩展了IMyAidlInterface接口,在Stub类中定义了asInterface方法,这个方法的作用是用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的【如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象】 简单来说,就是对于不同进程返回了Proxy对象,即这个对象就是给客户端指明了具体调用什么,也就是说这个 asInterface 方法返回的是一个远程接口具备的能力(有什么方法可以调用)。

/*** Cast an IBinder object into an com.cop.ronghw.study_android_exact.IMyAidlInterface interface,* generating a proxy if needed.*/public static com.cop.ronghw.study_android_exact.IMyAidlInterface asInterface(android.os.IBinder obj) {if ((obj == null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin != null) && (iin instanceof com.cop.ronghw.study_android_exact.IMyAidlInterface))) {return ((com.cop.ronghw.study_android_exact.IMyAidlInterface) iin);}return new com.cop.ronghw.study_android_exact.IMyAidlInterface.Stub.Proxy(obj);}

Proxy 接下来再简单分析一下Stub.Proxy类,此代理类的作用是构造数据、封装数据,将数据发送给server服务端。代理类Proxy实现了IMyAidlInterface接口,并实现了我们刚刚说的静态方法,如果客户端想使用getFile方法,必须先经过代理Proxy,在Proxy的getFile方法中封装数据,通过IBinder的引用发起IPC调用,并到服务端的方法。

private static class Proxy implements com.cop.ronghw.study_android_exact.IMyAidlInterface {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/@Overridepublic void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeInt(anInt);_data.writeLong(aLong);_data.writeInt(((aBoolean) ? (1) : (0)));_data.writeFloat(aFloat);_data.writeDouble(aDouble);_data.writeString(aString);boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);return;}_reply.readException();} finally {_reply.recycle();_data.recycle();}}// 定义方法@Overridepublic java.lang.String getFile() throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.lang.String _result;try {_data.writeInterfaceToken(DESCRIPTOR);boolean _status = mRemote.transact(Stub.TRANSACTION_getFile, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().getFile();}_reply.readException();_result = _reply.readString();} finally {_reply.recycle();_data.recycle();}return _result;}//发送数据@Overridepublic java.lang.String sendData(java.lang.String message) throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.lang.String _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(message);boolean _status = mRemote.transact(Stub.TRANSACTION_sendData, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().sendData(message);}_reply.readException();_result = _reply.readString();} finally {_reply.recycle();_data.recycle();}return _result;}public static com.cop.ronghw.study_android_exact.IMyAidlInterface sDefaultImpl;}

onTransact 方法:onTransact方法 运行在服务端中的 Binder 线程池中。客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法来处理。如果此方法返回 false,那么客户端的请求就会失败。此方法包含了以下参数:

  • code : 确定客户端请求的目标方法是什么。即确定客户端调用哪个服务端的实际处理业务的方法,比如getFile或sendData方法
  • data : 如果目标方法有参数的话,就从data取出目标方法所需的参数。
  • reply :当目标方法执行完毕后,如果目标方法有返回值,就向reply中写入返回值。
  • flag:附加操作标志。对于普通RPC,可以是0,对于单向RPC,可以是FLAG_ONEWAY
    也就是说,这个 onTransact方法 就是服务端处理的核心,接收到客户端的请求,并且通过客户端携带的参数,执行完服务端的方法,返回结果。

到此为止我们分析了Stub类的作用,通过分析可发现,通过aidl生成的java文件必须都包括在客户端和服务端代码中,两端都必须有这个文件。这个文件本文只是描述了大概,只是为了让大家清楚此文件的作用和如何发挥作用的。具体的交互过程还是要看binder底层驱动,由于本人能力有限,这个后面再去分析学习,然后再分享给大家。

到目前为止aidl文件就先分析到这里吧。下文我们再通过客户端与服务端代码案例来穿插讲解aidl生成的java文件如何使用的。

总结

本文主要介绍了跨进程通信涉及的技术点,也着重分析了跨进程通信的几种方式,并让大家简单了解了binder的机制原理,紧接着重点介绍了aidl文件的使用步骤及创建方式,又介绍了aidl文件的语法及做用。最后重点介绍了aidl文件如何生成java文件,并简要分析了此java文件在客户端与服务端之间的使用过程,及部分系统调用原理。

下文我们会通过客户端和服务端跨进程的案例来进一步了解通信机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/707944.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(unity学习)一些效果的学习

一、学习视频 【Unity教程】零基础带你从小白到超神 二、效果实现 三、问题解决 Unity 点击UI与点击屏幕冲突的解决方案 关于unity UI界面操作与场景内操作不冲突问题

Unity安装与简单设置

安装网址&#xff1a;https://unity.cn 设置语言&#xff1a; 设置安装位置&#xff1a;否则C盘就会爆了 获取一个个人的资格证&#xff1a; 开始安装&#xff1a; 安装完毕。 添加模块&#xff1a;例如简体中文 新建项目&#xff1a; 布局2*3、单栏布局、 设置…

4. client-go 编程式交互

Kubernetes 系统使用 client-go 作为 Go 语言的官方编程式交互客户端库&#xff0c;提供对 Kubernetes API Server 服务的交互访问。Kubernetes 的源码中已经集成了 client-go 的源码&#xff0c;无须单独下载。client-go 源码路径为 vendor/k8s.io/client-go。 开发者经常使用…

前端架构: 脚手架之包管理工具的案例对比及workspace特性的基本使用

Npm WorkSpace 特性 1 &#xff09;使用或不使用包管理工具的对比 vue-cli 这个脚手架使用 Lerna 管理&#xff0c;它的项目显得非常清晰在 vue-cli 中包含很多 package 点开进去&#xff0c;每一个包都有package.json它里面有很多项目&#xff0c;再没有 Lerna 之前去维护和管…

threehit二次注入案例

君衍. 一、环境搭建1、conn.php源码&#xff1a;2、register.php源码3、login.php源码4、index.php源码5、demo.php源码 二、数据库环境搭建1、注意点一2、注意点二报错原因 三、复现过程1、user12、user23、user34、user45、user56、user6-name7、user7-table8、user8-column9…

Python编程实验五:文件的读写操作

目录 一、实验目的与要求 二、实验内容 三、主要程序清单和程序运行结果 第1题 第2题 四、实验结果分析与体会 一、实验目的与要求 &#xff08;1&#xff09;通过本次实验&#xff0c;学生应掌握与文件打开、关闭相关的函数&#xff0c;以及与读写操作相关的常用方法的…

vue中scss样式污染引发的思考

新做了一个项目&#xff0c;就是在登录后&#xff0c;就会产生左侧菜单的按钮颜色不一样。 然后发现样式是从这里传过来的 然后发现是登录页面的css给污染了 就是加了scope就把这个问题解决了 然后想总结一下这个思路&#xff1a;就是如何排查污染样式&#xff0c; 如果出现…

postman测试接口

1、postman测试接口 &#xff08;1&#xff09;首先安装postman 下载地址&#xff1a;Download Postman | Get Started for Free 选择对应版本下载&#xff0c;然后安装即可 &#xff08;2&#xff09;使用postman发送请求 比如以下这个请求例子&#xff1a; 使用postman发…

SpringBoot集成EasyExcel快速人们

目录 1.背景介绍 2.EasyExcel的使用 1.添加依赖 2.相关代码准备 1.实体类 2.ExcelUtil工具类 3.写入控制类 1.背景介绍 EasyExcel 是阿里巴巴开发的一款基于 Java 的专业化 Excel 操作工具&#xff0c;主要用于在 Java 应用程序中快速、高效地读写 Excel 文件。EasyExce…

Git 指令深入浅出【2】—— 分支管理

Git 指令深入浅出【2】—— 分支管理 分支管理1. 常用分支管理指令2. 合并分支合并冲突合并模式 3. 实战演习 分支管理 1. 常用分支管理指令 # 查看本地分支 git branch# 查看远程分支 git branch -r# 查看全部分支 git branch -aHEAD 指向的才是当前的工作分支 # 查看当前分…

开源的 Python 数据分析库Pandas 简介

阅读本文之前请参阅-----如何系统的自学python Pandas 是一个开源的 Python 数据分析库&#xff0c;它提供了高性能、易用的数据结构和数据分析工具。Pandas 特别适合处理表格数据&#xff0c;例如时间序列数据、异构数据等。以下是对 Pandas 的简明扼要的介绍&#xff0c;包括…

基于springboot实现旅游路线规划系统项目【项目源码+论文说明】

基于springboot实现旅游路线规划系统演示 摘要 随着互联网的飞速发展以及旅游产业的逐渐升温&#xff0c;越来越多人通过互联网获取更多的旅游信息&#xff0c;包括参考旅游文纪等内容。通过参考旅游博主推荐的旅游景点和规划线路&#xff0c;参考计划着自己的旅行&#xff0c…

【《高性能 MySQL》摘录】第 8 章 优化服务器设置

文章目录 8.1 MySQL 配置的工作原理8.1.1 语法、作用域和动态性8.1.2 设置变量的副作用8.1.3 入门8.1.4 通过基准测试迭代优化 8.2 什么不该做8.3 创建MySQL配置文件8.3.1 检查 MySQL 服务器状态变量 8.4 配置内存使用8.4.1 MySQL 可以使用多少内存8.4.2 每个连接需要的内存8.4…

STL常见容器(list容器)---C++

STL常见容器目录&#xff1a; 6.list容器6.1 list基本概念6.2 list构造函数6.3 list 赋值和交换6.4 list 大小操作6.5 list 插入和删除6.6 list 数据存取6.7 list 反转和排序6.8自定义排序案例 6.list容器 6.1 list基本概念 功能&#xff1a; 将数据进行链式存储&#xff1b; …

Flutter输入框换行后自适应高度

Flutter输入框换行后输入框高度随之增加 效果 设计思想 通过TextEditingController在build中监听输入框&#xff0c;输入内容后计算输入框高度然后自定义适合的值&#xff0c;并且改变外部容器高度达到自适应高度的目的 参考代码 //以下代码中的值只适用于案例&#xff0c;…

MyBatis 学习(一)之 MyBatis 概述

目录 1 MyBatis 介绍 2 MyBatis 的重要组件 3 MyBatis 执行流程 4 参考文档 1 MyBatis 介绍 MyBatis 是一个半自动化的 ORM &#xff08;Object-Relational Mapping&#xff0c;对象关系映射&#xff09;持久层框架&#xff0c;它允许开发者通过 XML 或注解将对象与数据库中…

docker的简介--安装--操作命令

1.docker的简介 1.1docker是什么 用一句话来说docker就是一个新一代虚拟化技术 Docker是一种开源的平台&#xff0c;用于开发、交付和运行应用程序。它允许开发人员将应用程序和它们的依赖打包在一个容器中&#xff0c;然后部署到任何支持Docker的环境中。Docker的主要特点包括…

【React架构 - Scheduler中的MessageChannel】

前序 我们都知道JS代码是在浏览器5个进程(下面有介绍)中渲染进程中的Js引擎线程执行的&#xff0c;其他还有GUI渲染线程、定时器线程等&#xff0c;而页面的布局和绘制是在GUI线程中完成的&#xff0c;这些线程之间是互斥的&#xff0c;所以在执行Js的同时会阻塞页面的渲染绘制…

android应用开发基础知识,安卓面试2020

第一章&#xff1a;设计思想与代码质量优化 1、设计思想六大原则 2、三大设计模式 3、数据结构 4、算法 第二章&#xff1a;程序性能优化 1、启动速度和执行效率优化 2、布局检测与优化 3、内存优化 4、耗电优化 5、网络传输与数据存储优化 6、APK大小优化 7、屏幕适配 8、…

用Java语言创建的Spring Boot项目中,如何传递List集合呢?

前言&#xff1a; 在上篇文章中&#xff0c;用Java语言创建的Spring Boot项目中&#xff0c;如何传递数组呢&#xff1f;&#xff1f;-CSDN博客&#xff0c;我们了解到Spring Boot项目中如何传递数组&#xff0c;但是&#xff0c;对于同类型的List集合&#xff0c;我们又该如何…