默认情况下,一个应用不管有多少个 Activity、Service 或其他组件,它们都是运行在一个进程上,但是我们可以安排 Service 运行一个新的进程上,但是不同进程之间应该如何通信呢?当需要在不同的进程之间传递对象时,应该怎么做呢?AIDL(Android Interface Definition Language) 便是解决这一问题的钥匙。
使用 AIDL 并不是难事,但是比较繁琐,并且一不小心容易出错。好在 Android Dev Guide 的 Designing a Remote Interface Using AIDL 对这个问题讲解非常详细,再结合 Android APIDemo 中的 Remote Service Binding 给出了的示例,这都给了开发者提供了非常到位的帮助。以下内容就是我结合二者,整理出来的笔记,力求真理,但能力有限,差错难免,请读者坚持自己的判断力,本文只供参考,且不可尽信。
一、使用 AIDL 实现 IPC
面对问题,应统揽全局,归纳阶段,划定步骤,共得出四大步,分列如下:
-
创建.aidl 文件
- 在这个文件里定义 method 和 field 把 .aidl 文件加入到 makefile 中去
- 如果使用 Eclipse 则 ADT 会帮你管理 实现接口方法
- AIDL 编译器会根据接口生成一个用 Java 语言编写的interface,这个 interface 有一个抽象的内部类,名字为 Stub,你必须创建一个类,继承于它,并且实现 .adil 文件中所声明的方法 公开接口给客户端
- 如果创建的是 service,则应该继承自 Service,并且重载 Service.onBind() 返回实现接口的类的实例
这四个步骤在 Remote Service Binding 中均有所呈现,以下分开阐述。Remote Service Binding 共包含有两个 .java 文件,三个 .aidl 文件,物理结构比较简单,但是逻辑结构就不那么简单,以下用 Class Diagram 来展示其中的关系。
1、创建.aidl 文件
AIDL 语法简单,用来声明接口,其中的方法接收参数和返回值,但是参数和返回值的类型是有约束的,且有些类型是需要 import,另外一些则无需这样做。
AIDL 支持的数据类型划分为四类,第一类是 Java 编程语言中的基本类型,第二类包括 String、List、Map 和 CharSequence,第三类是其他 AIDL 生成的 interface,第四类是实现了 Parcelable protocol 的自定义类。
其中,除了第一类外,其他三类在使用时均需要特别小心。
使用第二类时,首先需要明白这些类不需要 import,是内嵌的。其次注意在使用 List 和 Map 此二者容器类时,需注意其元素必须得是 AIDL 支持的数据类型,List 可支持泛型,但是 Map 不支持,同时另外一端负责接收的具体的类里则必须是 ArrayList 和 HashMap。
使用第三、四类时,需要留意它们都是需要 import 的,但是前者传递时,传递的是 reference,而后者则是 value。
在创建 .aidl 文件的过程中,应该注意一旦 method 有参数,则需注意在前面加上 in, out 或 inout,它们被称为 directional tag,但是对于基本类型的参数,默认就是 in,并且不会是其他值。
Remote Service Binding 共包括了三个 .aidl 文件,分别是IRemoteService.aidl、IRemoteServiceCallback.aidl、ISecondary.aidl,通过它们可以看到如何使用第一类和第三类的数据类型,稀罕的是,看不到第二类、第四类数据类型的使用,也没有看到 directional tag。
2、实现 Interface
AIDL 为你生成一个 interface 文件,文件名称和 .aidl 文件相同。如果使用 Eclipse 插件,则 AIDL 会在构建过程中自动运行,如果不使用插件,则需要先使用 AIDL。
生成的 interface 会包含一个抽象内部类 Stub,它声明了在 .aidl 文件里的所有方法。Stub 也定义了一些帮助方法,比较常用的有 asInterface(),其接收一个 IBinder 作为参数,并且返回一个 interface 的实例用来调用IPC方法。
private static INotificationManager sService;
static private INotificationManager getService()
{
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
要实现 interface,需要继承 Stub,实现其方法,这在 RemoteService 和 RemoteServiceBinding 都可以找到相关代码。
这个环节是重中之重,需要特别小心的有两点,其一是抛出的所有异常均不会发给调用者;其二是IPC调用是同步的,这意味IPC服务一旦花费较长时间完成的话,就会引起ANR,应该将这样的操作放在单独的线程里。
3、向客户端公开 Interface
独乐乐不如众乐乐,需要将服务公开出去,要达成这个目的,须得创建一个 Service 的子类,并且实现 Service.onBind(Intent),通过这个方法将实现了接口的类的实例返回回来。通过查看 RemoteService 一目了然。
@Override
public IBinder onBind(Intent intent) {
// Select the interface to return. If your service only implements
// a single interface, you can just return it here without checking
// the Intent.
if (IRemoteService.class.getName().equals(intent.getAction())) {
return mBinder;
}
if (ISecondary.class.getName().equals(intent.getAction())) {
return mSecondaryBinder;
}
return null;
}
4、使用Parcelables传值
前文中提到 Remote Servcie Binding 没有使用第四类数据类型作为参数,这是示例的不足,要想让一个类变成第四类,需要遵照以下步骤:
- 引入 Parcelable 接口
- 实现 writeToParcel(Parcel out)
- 增加一个静态的field,其实现 Parcelable.Creator 接口
- 创建一个 .aidl 文件,用以声明你的 parcelables 类
在 Designing a Remote Interface Using AIDL 中,类 Rect 是一个不错的示例,弥补了 Remote Service Binding 的不足。
二、调用 IPC 方法
万事具备,只欠东风,IPC 备妥,只待调用。在 Remote Service Binding 中,RemoteServiceBinding 正是 IPC 的调用者,
既然要使用接口,那就先声明 interface 类型的变量,
/** The primary interface we will be calling on the service. */
IRemoteService mService = null;
/** Another interface we use on the service. */
ISecondary mSecondaryService = null;
public void onServiceConnected(ComponentName className,
IBinder service) {
mService = IRemoteService.Stub.asInterface(service);
// ... 以下省略
}
}
接着别忘了调用 Context.bindService(),完成任务以后,调用 Context.unbindService()。如果在 connection 中断的情况下,调用 IPC 方法,你会遇到 DeadObjectException,这是 remote method 能抛出的唯一异常。