Android Interface Definition Language(AIDL)
AIDL与您可能使用过的其他接口语言(IDL)类似。可以利用它定义客户端与服务端均认可的编程接口,以便二者能够使用进程间通信(IPC)进行相互通信。在Android中,一个进程通常无法访问另一个进程的内存。因此为了进行通信,进程需要将对象分解成可供操作系统理解的原语,并将其编组为可供应用操作的对应。
注意:只有在需要不同应用的客户端通过IPC方式访问服务,并且希望在服务中进行多线程处理时,才有必要使用AIDL。如果无需要跨进程执行并发IPC,则应用通过实现Binder来创建接口。仅想执行IPC,不需要多线程处理,请使用Messenger来实现接口。
简单一点的说:
- 只有需要跨进程调用时,才使用AIDL,否则通过实现
Binder
类来创建接口即可。 - AIDL接口的实现必须是线程安全的。如果调用来自同一远程对象上的某个线程,则该调用将依次抵达接口器端
-
oneway
关键字用于修改远程调用的行为。使用此关键字后,远程调用不会屏蔽,而是是发送事务数据并立即返回。最终接收该数据时,接口的实现会将其视为来自Binder线程池的常规调用(普通的远程调用)。如果oneway
用于本地调用,则不会有任何影响,且调用仍为同步调用。
定义AIDL接口
在 .aidl 文件中使用 Java 编程语言的语法定义 AIDL 接口,然后将其保存至应用的源代码(在 src/ 目录中)内,这类应用会托管服务或与服务进行绑定。
在构建每个包含 .aidl 文件的应用时,Android SDK 工具会生成基于该 .aidl 文件的 IBinder 接口,并将其保存到项目的 gen/ 目录中。服务必须视情况实现 IBinder 接口。然后,客户端应用便可绑定到该服务,并调用 IBinder 中的方法来执行 IPC。
执行以下步骤创建AIDL绑定服务:
- 创建.aidl文件:些文件定义了带有方法签名的接口
- 实现接口:Android SDK中的aidl命令行工具会生成一个基于Java的抽象实现。接口中拥有一个名为Stub的内部抽象类,用于扩展Binder类并实现AIDL中的方法。服务提供者必须实现这个Stub,用于对外提供服务。
- 向客户端公开接口:实现Service并重写onBind(),从而返回第二步中的实现类。
注意:如果您在首次发布 AIDL 接口后对其进行更改,则每次更改必须保持向后兼容性,以免中断其他应用使用您的服务。换言之,由于只有在将您的 .aidl 文件复制到其他应用后,才能使其访问服务接口,因而您必须保留对原始接口的支持。
1. 创建.aidl文件
AIDL默认支持以下数据类型:
- Java中的所有基本类型(如int、long、char、boolean等)
- String
- CharSequence
- List:List中的所有元素必须是以上列表中支持的数据类型,或者是AIDL声明的其他接口或Parcelable类型。ArrayList
- Map:List中的所有元素必须是以上列表中支持的数据类型,或者是AIDL声明的其他接口或Parcelable类型。HashMap
即使您在与接口相同的包内定义上方未列出的附加类型,亦须为其各自加入一条 import 语句。-p>
定义服务接口时,需要注意:
- 方法可带0个或者多个参数,返回值或者空值
- 所有非基本数据类型均需要指示数据走向的方向标记。标记可以是in、out或者inout。基本数据类型默认方向标记为in,不能是其它标记。
- AIDL对所有非基本数据类型引用,需要该类实现Parcelable接口,并使用类名创建一个同名的aidl文件。
- 可以在AIDL中定义String常量和int常量。比如:const int VERSION = 1。
-
方法调用由 transact() 代码分派,该代码通常基于接口中的方法索引。由于这会增加版本控制的难度,因此您可以向方法手动配置事务代码:void method() = 10;。??? - 使用@nullable注释可空参数或者返回 类型
下面列出in
、out
和inout
,oneway
关键字对AIDL实现的影响:
-
in
:当AIDL方法中的参数用in修饰时,表示数据流向:client -> server。server能正常收到client传递的值 -
out
:当AIDL方法中的参数用out修饰时,表示数据流向:client <- server。即使调用方传递了参数server端也不会读取到在client的赋值;当调用结束后,client端通过当前传递参数的实例,能拿到server对该参数的更新 -
inout
:当AIDL方法中的参数用inout修饰时,表示数据流向:client <-> server。server能正常收到client传递的值;当调用结束后,client端通过当前传递参数的实例,也能拿到server对该参数的更新。 -
oneway
:影响transact方法调用时传递的flags值。默认情况下flags = 0,跨进程的操作是同步的,也就是说transact方法会阻塞当前调用线程,直到远程服务端返回。当flags = FLAG_ONEWAY时,表示对transact方法调用单向调用,不会阻塞当前调用线程,而是立即返回。最终在接收数据时,接口的实现会将其看作是来自Binder线程池的常规调用。当oneway
用于本地调用时,不会有任何影响,调用属于何种类型(阻塞,异步)就完全依赖本地实现了。oneway修饰的方法不能有返回值。
实际上不管是有没有oneway修饰的AIDL方法,在服务端实现时都是运行在Binder线程池中的,只不过会没有oneway修饰的方法实现,在客户端会阻塞在当前调用线程直到返回才能继续执行下一个操作。
// flags = 0
boolean _status = mRemote.transact(
Stub.TRANSACTION_fetchTodayCallback,
_data, _reply,
0);
// flags = FLAG_ONEWAY
boolean _status = mRemote.transact(
Stub.TRANSACTION_asyncFetchTodayCallback,
_data, null,
android.os.IBinder.FLAG_ONEWAY);
2. 实现接口
当构建应用时,Android SDK 工具会生成以 .aidl 文件命名的 .java 接口文件。生成的接口包含一个名为 Stub 的子类(例如,YourInterface.Stub),该子类是其父接口的抽象实现,并且会声明 .aidl 文件中的所有方法。
Stub还定义了几个辅助方法,其中最值得注意的是asInterface(),该方法会接收IBinder(通过是传递给客户端onServiceConnected回调方法的参数),并返回Stub接口的实例。
在实现 AIDL 接口时,您应注意遵守以下规则:
由于无法保证在主线程上执行传入调用,因此您一开始便需做好多线程处理的准备,并对您的服务进行适当构建,使其达到线程安全的标准。
默认情况下,RPC 调用是同步调用。如果您知道服务完成请求的时间不止几毫秒,则不应从 Activity 的主线程调用该服务,因为这可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 通常,您应从客户端内的单独线程调用服务。
引发的任何异常都不会回传给调用方。
3. 向客户端公开接口
通过实现Service中的onBind方法返回上一步实现的接口,完成对外提供服务的能力
如果客户端和服务在不同的应用内,那么客户端应用的里必须包含访问服务接口的AIDL抽象实现,或者由服务提供方提供相应的访问SDK。
当客户端在onServiceConnected()回调中收到IBinder时,通过YourServiceInterface.Stub.asInterface(service)返回YourServiceInterface类型的实例。如果在同一进程,该实例与Service中onBind返回的是同一实例,否则返回的是一个远程代理实例YourServiceInterface.Stube.Proxy
通过IPC传递对象
可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。不过,您必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持 Parcelable 接口。支持 Parcelable 接口很重要,因为 Android 系统能通过该接口将对象分解成可编组至各进程的原语。
如要创建支持 Parcelable 协议的类,您必须执行以下操作:
- 让您的类实现 Parcelable 接口。
- 实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。
- 为您的类添加 CREATOR 静态字段,该字段是实现 Parcelable.Creator 接口的对象。
- 最后,创建声明 Parcelable 类的 .aidl 文件(遵照下文 Rect.aidl 文件所示步骤)。如果您使用的是自定义编译进程,请勿在您的构建中添加 .aidl 文件。此 .aidl 文件与 C 语言中的头文件类似,并未经过编译。
package android.graphics;
// Declare Rect so AIDL can find it and
// knows that it implements
// the parcelable protocol.
parcelable Rect;
带软件包参数(包含 Parcelable 类型)的方法
如果您的 AIDL 接口包含接收软件包作为参数(预计包含 Parcelable 类型)的方法,则在尝试从软件包读取之前,请务必通过调用 Bundle.setClassLoader(ClassLoader) 设置软件包的类加载器。否则,即使您在应用中正确定义 Parcelable 类型,也会遇到 ClassNotFoundException。例如,
如果您有 .aidl 文件:
// IRectInsideBundle.aidl
package com.example.android;
/** Example service interface */
interface IRectInsideBundle {
/** Rect parcelable is stored in the bundle with key "rect" */
void saveRect(in Bundle bundle);
}
如下方实现所示,在读取 Rect 之前,ClassLoader 已在 Bundle 中完成显式设置
private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
public void saveRect(Bundle bundle){
bundle.setClassLoader(getClass().getClassLoader());
Rect rect = bundle.getParcelable("rect");
process(rect); // Do more with the parcelable.
}
};
调用IPC方法
如要调用通过 AIDL 定义的远程接口,调用类必须执行以下步骤:
- 在项目的 src/ 目录中加入 .aidl 文件。(也可以在任意地方编写aidl文件,通过android sdk提供的aidl命令行工具生成同名的java抽象实现)
- 声明一个 IBinder 接口实例(基于 AIDL 生成)。
- 实现 ServiceConnection。
- 调用 Context.bindService(),从而传入您的 ServiceConnection 实现。
- 在 onServiceConnected() 实现中,您将收到一个 IBinder 实例(名为 service)。调用 YourInterfaceName.Stub.asInterface((IBinder)service),以将返回的参数转换为 YourInterface 类型。
- 调用您在接口上定义的方法。您应始终捕获 DeadObjectException 异常,系统会在连接中断时抛出此异常。您还应捕获 SecurityException 异常,当 IPC 方法调用中两个进程的 AIDL 定义发生冲突时,系统会抛出此异常。
- 如要断开连接,请使用您的接口实例调用 Context.unbindService()。