前面写了 Bind Service 的两种实现方式,接下来转一篇贴子来简述一下最后一种实现方式
第三种:使用 AIDL
前面讲的使用 Messenger 技术,其实是基于 AIDL架构的,但是 Messenger 使用一个队列来处理所有的请求,这样一来,就无法进行多线程的并发了。所以,如果你想同时接受并处理多个 client 的请求,那么请使用 AIDL 来实现,但这样的话,你需要小心地进行同步处理了哦~
默认情况下,一个应用不管有多少个 Activity、Service 或其他组件,它们都是运行在一个进程上,但是我们可以安排 Service 运行一个新的进程上,但是不同进程之间应该如何通信呢?当需要在不同的进程之间传递对象时,应该怎么做呢?AIDL(Android Interface Definition Language) 便是解决这一问题的钥匙。
使用 AIDL 并不是难事,但是比较繁琐,并且一不小心容易出错。好在 Android Dev Guide 的 "AIDL" 章节(在 "Dev Guide " 左侧列表的最下面)对这个问题讲解非常详细,再结合 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 接口的自定义类。
其中,除了第一类外,其他三类在使用时均需要特别小心。
使用第二类时,首先需要明白这些类不需要 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 文件里的所有方法。要想实现你在 .aidl 文件里定义的接口,就必须实现这个Stub类,如下:
/**
* This implementation is used to receive callbacks from the remote
* service.
*/
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
/**
* This is called by the remote service regularly to tell us about
* new values. Note that IPC calls are dispatched through a thread
* pool running in each process, so the code executing here will
* NOT be running in our main thread like most other things -- so,
* to update the UI, we need to use a Handler to hop over there.
*/
public void valueChanged(int value) {
mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
}
};
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;
}
其中的 mBinder 和 mSecondaryBinder 分别是实现了 IRemoteService 和 ISecondary 接口的类的实例。
4、使用Parcelables传值
前文中提到 Remote Servcie Binding 没有使用第四类数据类型作为参数,这是示例的不足,要想让一个类变成第四类,需要遵照以下步骤:
① 引入 Parcelable 接口
② 实现 writeToParcel(Parcel out)
③ 增加一个静态的field,其实现 Parcelable.Creator 接口
④ 创建一个 .aidl 文件,用以声明你的 parcelables 类
在 "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;
实现 ServiceConnection,在 onServiceConnected(ComponentName className, IBinder service) 中完成对 mService 和 mSecondaryService 的赋值。
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mService = IRemoteService.Stub.asInterface(service);
// ... 以下省略
}
}
接着别忘了调用 Context.bindService(),完成任务以后,调用 Context.unbindService()。如果在 connection 中断的情况下,调用 IPC 方法,你会遇到 DeadObjectException,这是 remote method 能抛出的唯一异常。