本篇主要记录内容:1、aidl基本使用

                                 2、in、 out 、inout、onway关键字含义

                                 3、aidl在使用时对线程的注意事项。跨进程通信时,是在本地的一个binder线程池中执行的。

                                 4、传递bundle对象时,需要注意的点

AIDL通信方式

在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。

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

(跨进程通信需要进行多线程处理,使用aidl。 跨进程不需要处理多线程,使用Messenger) 

  • 远程进程的调用分派自线程池,且平台会在您自己的进程内部维护该线程池。您必须为来自未知线程,且多次调用同时发生的传入调用做好准备。换言之,AIDL 接口的实现必须基于完全的线程安全。如果调用来自同一远程对象上的某个线程,则该调用将依次抵达接收器端。

aidl接口会在一个单独的线程池中被调用,并且这个线程池在客户端进程中维护,我们知道aidl是基于Service实现的多进程通信,所以在远程service中,实现的aidl方法需要保证多线程通信安全。简单来说就是处理多线程调用统一对象的问题。但是如果多次调用都来自同一线程,那么调用将依次到达,就是按顺序咯。

  • oneway 关键字用于修改远程调用的行为。使用此关键字后,远程调用不会屏蔽,而只是发送事务数据并立即返回。最终接收该数据时,接口的实现会将其视为来自 Binder 线程池的常规调用(普通的远程调用)。如果 oneway 用于本地调用,则不会有任何影响,且调用仍为同步调用。

被oneway修饰了的方法不可以有返回值,也不可以有带out或inout的参数。

默认情况下,远程调用会阻塞。使用oneway关键字,远程调用不会阻塞;它只是发送事务数据并立即返回。最终是在binder线程池中调用。

 

先介绍下in 、out 、inout

这个对接口参数设置设置数据流向

AndroidHint 的文章就讲的很清楚。总结就是:

1、所有非基本类型的参数都需要一个定向tag来表明数据是如何走向的。基本数据类型默认是in,而且不能是其他tag

2、 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可以在服务端与客户端之间双向流通。其中的数据流向是针对在客户端中的那个传入方法的对象而言的。(针对参数而言)

3、对于in,服务端将会收到客户端对象的完整数据,但是客户端对象不会因为服务端对传参的修改而发生变动。

4、对于out,服务端将会收到客户端对象,该对象不为空,但是它里面的字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动。

5、对于inout ,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

 

使用步骤:

1、在src目录下创建.aidl文件

就是创建一个dial文件,这个文件是接口,make project之后,会生成改接口的类。

在接口中申明方法。

默认情况下,AIDL 支持下列数据类型:

  • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等)
  • String
  • CharSequence
  • List List 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。您可选择将 List 用作“泛型”类(例如,List<String>)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList。
  • Map Map 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap。

即使您在与接口相同的包内定义上方未列出的附加类型,亦须为其各自加入一条 import 语句。

就是支持基本类型,如果是自定义的类型必须实现Parcelable。并且需要手动使用Import引入。

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

  • 方法可带零个或多个参数,返回值或空值。
  • 所有非原语参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout(见下方示例)。 原语默认为 in,不能是其他方向。
  • 生成的 IBinder 接口内包含 .aidl 文件中的所有代码注释(import 和 package 语句之前的注释除外)。
  • 您可以在 ADL 接口中定义 String 常量和 int 字符串常量。例如:const int VERSION = 1;。
  • 方法调用由 transact() 代码分派,该代码通常基于接口中的方法索引。由于这会增加版本控制的难度,因此您可以向方法手动配置事务代码:void method() = 10;。
  • 使用 @nullable 注释可空参数或返回类型。

 

如果使用到自定义类型,比如接口方法中需要一个UserBean对象。那么需要在客户端代码中创建这个UserBeean对象,实现Parcelable。然后在aidl的相同目录下创建UserBean.aidl 文件,申明如下 

 

android aidl 跨进程通信 android跨进程通信应用场景_跨进程通信

 在需要使用到的aidl接口中手动引入才可以使用。

android aidl 跨进程通信 android跨进程通信应用场景_跨进程通信_02

2、在远程服务创建刚刚的接口,并调用内部类Stub实例化

构建应用时,Android SDK 工具会生成以 .aidl 文件命名的 .java 接口文件。生成的接口包含一个名为 Stub 的子类(例如,YourInterface.Stub),该子类是其父接口的抽象实现,并且会声明 .aidl 文件中的所有方法。

在远程服务上创建该类实例

android aidl 跨进程通信 android跨进程通信应用场景_跨进程通信_03

并在onBind中返回即可

android aidl 跨进程通信 android跨进程通信应用场景_android aidl 跨进程通信_04

 

 

需要特别注意的点:

在实现 AIDL 接口时,您应注意遵守以下规则:

  • 由于无法保证在主线程上执行传入调用,因此您一开始便需做好多线程处理的准备,并对您的服务进行适当构建,使其达到线程安全的标准。
  • 默认情况下,RPC 调用是同步调用。如果您知道服务完成请求的时间不止几毫秒,则不应从 Activity 的主线程调用该服务,因为这可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 通常,您应从客户端内的单独线程调用服务。
  • 您引发的任何异常都不会回传给调用方。

 

如果您的 AIDL 接口包含接收软件包作为参数(预计包含 Parcelable 类型)的方法,则在尝试从软件包读取之前,请务必通过调用 Bundle.setClassLoader(ClassLoader) 设置软件包的类加载器。否则,即使您在应用中正确定义 Parcelable 类型,也会遇到 ClassNotFoundException。

android aidl 跨进程通信 android跨进程通信应用场景_跨进程通信_05

 

android aidl 跨进程通信 android跨进程通信应用场景_android aidl 跨进程通信_06

 

 

3、客户端监听

android aidl 跨进程通信 android跨进程通信应用场景_跨进程通信_07

通过bindService方法中的ServiceConnection方法获取到ibner对象,调用Stub类里面的asInferface方法转换。

最后总结一下流程

如要调用通过 AIDL 定义的远程接口,调用类必须执行以下步骤:

  1. 在项目的 src/ 目录中加入 .aidl 文件。
  2. 声明一个 IBinder 接口实例(基于 AIDL 生成)。
  3. 实现 ServiceConnection。
  4. 调用 Context.bindService(),从而传入您的 ServiceConnection 实现。
  5. 在 onServiceConnected() 实现中,您将收到一个 IBinder 实例(名为 service)。调用 YourInterfaceName.Stub.asInterface((IBinder)service),以将返回的参数转换为 YourInterface 类型。
  6. 调用您在接口上定义的方法。您应始终捕获 DeadObjectException 异常,系统会在连接中断时抛出此异常。您还应捕获 SecurityException 异常,当 IPC 方法调用中两个进程的 AIDL 定义发生冲突时,系统会抛出此异常。
  7. 如要断开连接,请使用您的接口实例调用 Context.unbindService()。

有关调用 IPC 服务的几点说明:

  • 对象是跨进程计数的引用。
  • 您可以方法参数的形式发送匿名对象。