Google Doc:https://developer.android.google.cn/guide/components/aidl
AIDL:Android Interface Definition Language,即Android接口定义语言。可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。
注:只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder创建接口;或者,如果想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。无论如何,在实现 AIDL 之前,需要理解绑定服务。
从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板。
通过编写.aidl文件来设计想要暴露的接口,编译后会自动生成相应的.java文件,服务器将接口的具体实现写在Stub中,用IBinder对象传递给客户端,客户端bindService的时候,用asInterface的形式将IBinder还原成接口,再调用其中的方法。
注意: AIDL 接口的调用是直接函数调用。 调用来自本地进程还是远程进程中的线程,实际情况会有所差异。 具体而言:
- 来自本地进程的调用在发起调用的同一线程内执行。如果该线程是主 UI 线程,则该线程继续在 AIDL 接口中执行。 如果该线程是其他线程,则其便是在服务中执行对应代码的线程。 因此,只有在本地线程访问服务时,我们才能完全控制哪些线程在服务中执行(但如果真是这种情况,根本不应该使用 AIDL,而是应该通过实现 Binder 类创建接口)。
- 来自远程进程的调用分派自平台在自有进程内部维护的线程池。 必须为来自未知线程的多次并发传入调用做好准备。 换言之,AIDL 接口的实现必须是完全线程安全实现。
- oneway 关键字用于修改远程调用的行为。使用该关键字时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用进行接收。 如果 oneway 用于本地调用,则不会有任何影响,调用仍是同步调用。
AIDL支持的数据类型
- 基本数据类型(int、long、char、boolean、double、byte、short、float)
- String 和 CharSequence
- List、Map集合:
List/Map中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或声明的可打包类型。可选择将 List/Map 用作“通用”类(例如,List<String>、Map<String,Integer>)。
另一端实际接收的具体类始终是 ArrayList/HashMap,但生成的方法使用的是 List/Map 接口。 - 实现了 Parcelable 接口的对象
- AIDL本身接口也可以在AIDL文件使用
AIDL使用步骤
- 创建 .aidl 文件
此文件定义带有方法签名的编程接口。 - 实现接口
Android SDK 工具基于您的 .aidl 文件,使用 Java 编程语言生成一个接口。此接口具有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现方法。 - 向客户端公开该接口
实现 Service 并重写 onBind() 以返回 Stub 类的实现。
1)创建.aidl文件
定义服务接口时,需注意:
- 方法可带零个或多个参数,返回值或空值。
- 所有非原语参数都需要指示数据走向的方向标记。可以是 in、out 或 inout。
原语默认为 in,不能是其他方向。
定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为 in、out、inout 三种。
- in 表示数据只能由客户端流向服务端;
- out 表示数据只能由服务端流向客户端;
- inout 则表示数据可在服务端与客户端之间双向流通。
此外,如果AIDL方法接口的参数值类型是:基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in,所以除了这些类型外,其他参数值都需要明确标注使用哪种定向Tag。
注意:应该将方向限定为真正需要的方向,因为编组参数的开销极大。
- .aidl 文件中包括的所有代码注释都包含在生成的 IBinder 接口中(import 和 package 语句之前的注释除外)。
- 只支持方法;不能公开 AIDL 中的静态字段。
只需将 .aidl 文件保存在项目的 src/ 目录内,SDK 工具会在项目的 gen/ 目录中生成 IBinder 接口文件。生成的文件名与 .aidl 文件名一致,只是使用了 .java 扩展名(例如,IRemoteService.aidl 生成的文件名是 IRemoteService.java)。
.aidl文件示例
// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service, to do evil things with it. */
int getPid();
/** 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);
}
2)实现接口
Android SDK 工具根据 .aidl 文件生成的 .java 接口包括一个名为 Stub 的子类,这个子类是其父接口(例如,YourInterface.Stub)的抽象实现,用于声明 .aidl 文件中的所有方法。
注:Stub 还定义了几个帮助方法,其中 asInterface() 方法带 IBinder(通常便是传递给客户端 onServiceConnected() 回调方法的参数)并返回存根接口实例。
在实现 AIDL 接口时应注意以下几个规则:
- 由于不能保证在主线程上执行传入调用,因此一开始就需要做好多线程处理准备,并将服务正确地编译为线程安全服务。
- 默认情况下,RPC 调用是同步调用。如果明知服务完成请求的时间不止几毫秒,就不应该从 Activity 的主线程调用服务,因为这样做可能会使应用挂起,从而导致ANR。通常应该从客户端内的单独线程调用服务。
- 引发的任何异常都不会回传给调用方。
3)向客户端公开该接口
为服务实现接口后,就需要向客户端公开该接口,以便客户端进行绑定。 要为服务公开该接口,需要扩展 Service 并实现 onBind(),以返回一个类实例,这个类实现了生成的 Stub。
public class RemoteService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
// Return the interface
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public int getPid(){
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
}
当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法返回的 mBinder 实例。
客户端还必须具有对 interface 类的访问权限,因此如果客户端和服务在不同的应用内,则客户端的应用 src/ 目录内必须包含 .aidl 文件(它生成 android.os.Binder 接口 — 为客户端提供对 AIDL 方法的访问权限)的副本。
当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 YourServiceInterface.Stub.asInterface(service) 以将返回的参数转换成 YourServiceInterface 类型。
IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Following the example above for an AIDL interface,
// this gets an instance of the IRemoteInterface, which we can use to call on the service
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
回调的支持
1)通过RemoteCallbackList
的 register(IInterface callback)
和register(IInterface callback,Object cookie)
方法注册回调,在多个回调时可以通过cookie判断是哪个回调;通过unregister(IInterface callback)
注销回调。
2)AIDL通过广播的方式去调用回调方法。其中RemoteCallbackList
的getBroadcastItem(int index)
方法可以获取对应的回调类,然后再通过invokeMethod
方法执行回调的对应方法。
示例:
/**
* 选择要执行的回调的方法
* @param operateType 回调的cookie(用来确认调用哪一个回调)
* @param methodName 方法名称
* @param classes 执行方法的参数类型
* @param args 执行方法的参数
*/
public void selectCallbackMethod(String operateType, String methodName,Class[] classes, Object... args) {
final int N = remoteCallbackList.beginBroadcast();
for (int i = 0; i < N; i++){
if (remoteCallbackList.getBroadcastCookie(i).equals(operateType)){
invokeMethod(remoteCallbackList.getBroadcastItem(i), methodName, classes, args);
}
}
//记得关闭广播
remoteCallbackList.finishBroadcast();
}
3)在服务onDestory
的时候调用kill
方法(kill方法说明:Disable this callback list. All registered callbacks are unregistered and the list is disabled so that future calls to {@link #register} will fail. This should be used when a Service is stopping, to prevent clients from registering callbacks after it is stopped.)
示例:
@Override
public void onDestroy() {
if (remoteCallbackList != null){
remoteCallbackList.kill();
}
super.onDestroy();
}
通过 IPC 传递对象
通过 IPC 接口可以把某个类从一个进程发送到另一个进程。 但是必须确保该类的代码对 IPC 通道的另一端可用,并且该类必须支持 Parcelable 接口(因为 Android 系统正是通过 Parcelable 接口将对象分解成可编组到各进程的原语)。
创建支持 Parcelable 协议的类,须执行以下操作:
- 让类实现 Parcelable 接口。
- 实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。
- 为该类添加一个名为 CREATOR 的静态字段,这个字段是一个实现 Parcelable.Creator 接口的对象。
- 最后,创建一个声明可打包类的 .aidl 文件。
AIDL 在它生成的代码中使用这些方法和字段将对象编组和取消编组。
示例
以下这个 Rect.aidl 文件可创建一个可打包的 Rect 类:
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
以下示例展示了 Rect 类如何实现 Parcelable 协议:
import android.os.Parcel;
import android.os.Parcelable;
public final class Rect implements Parcelable {
public int left;
public int top;
public int right;
public int bottom;
public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
public Rect createFromParcel(Parcel in) {
return new Rect(in);
}
public Rect[] newArray(int size) {
return new Rect[size];
}
};
public Rect() {
}
private Rect(Parcel in) {
readFromParcel(in);
}
public void writeToParcel(Parcel out) {
out.writeInt(left);
out.writeInt(top);
out.writeInt(right);
out.writeInt(bottom);
}
public void readFromParcel(Parcel in) {
left = in.readInt();
top = in.readInt();
right = in.readInt();
bottom = in.readInt();
}
}
调用IPC方法
调用类必须执行以下步骤,才能调用使用 AIDL 定义的远程接口:
- 在项目 src/ 目录中加入 .aidl 文件。
- 声明一个 IBinder 接口实例(基于 AIDL 生成)。
- 实现 ServiceConnection。
- 调用 Context.bindService(),以传入 ServiceConnection 实现。
- 在 onServiceConnected() 实现中,将收到一个 IBinder 实例(名为 service)。调用 YourInterfaceName.Stub.asInterface((IBinder)service),以将返回的参数转换为 YourInterface 类型。
- 调用在接口上定义的方法。需始终捕获 DeadObjectException 异常,它们是在连接中断时引发的;这将是远程方法引发的唯一异常。
- 如需断开连接,请使用接口实例调用 Context.unbindService()。
有关调用 IPC 服务的几点说明:
- 对象是跨进程计数的引用。
- 可以将匿名对象作为方法参数发送。