为了便于对本文的理解,请先阅读上一篇文章《Service详解_ BoundService》。上篇文章讲解了怎样用BoundService实现应用内其它组件与Service的交互,本文将讲解怎样用AIDL实现进程间通信。
AIDL简介
AIDL(Android接口定义语言)和其它接口定义语言一样,它允许你定义Client和Service约定好的编程接口,以实现两者的进程间通信(IPC)。在安卓系统中,一个进程通常不能访问另一进程的内存,所以需要把他们的对象分解成操作系统可以理解的原语,这样才能把对象传递给对方进程。Android系统已经替我们实现了对象分解到原语、原语组装成对象,我们要做的就是用AIDL定义好接口并正确使用。
AIDL与Binder的使用场景
BoundService不管是Remote Bind还是Local Bind都需要提供一个供Client调用的接口,按照接口的创建方式分为:
1、 用AIDL创建:如果有来自不同进程的Client访问你的Service,并且需要在Service中处理多线程任务,那么需要使用AIDL创建接口。
2、 通过扩展Binder类实现:如果只有来自同一进程的Client访问你的Service,则应该通过Binder创建接口
Server端实现
AIDL接口是用java语法定义,以.aidl文件保存,并保存在Service和Client的src/目录下。编译包含.aidl文件的APP时, Android SDK tools会根据.aidl文件生成IBinder interface,并保存到工程的/gen目录。Service实现IBinder接口,Client绑定到Service后,就可以调用IBinder中的函数进行进程间通信。使用AIDL创建Bounded Service的步骤如下:
1、 创建.aidl文件
.aidl文件是用java编程语言构建的,每一个aidl文件只能定义一个接口,每个接口中可以申明一个或多个方法,这些方法可以携带参数,也可以有返回值。参数和返回值可以是任何类型,甚至是其他的AIDL生成的接口。
默认情况下,AIDL支持下列数据类型:
•java编程语言中的所有基本类型(如int,long,char, boolean等)
•String
•CharSequence
•List
你必须用import来引入上面不支持的数据类型,即使这个数据类型的定义与你的接口在同一个包下。定义Service接口,需要注意以下事项:
•方法可以有0个或多个参数,可以有返回值或为void。
•所有非原始参数都需要用in、out或inout标记数据方向。原始参数默认用in标记。
•所有包含在.aidl文件中的注释(除开import和package之前的注释)都会包含在生成的IBinder接口中。
•aidl接口只支持方法,你不能在AIDL中暴露static字段。
下面是一个.aidl文件的例子
// IRemoteService.aidl
package com.jenny.example.service;
// Declare any non-default types here with import statements
interface IRemoteService {
int plus(int a, int b);
}
保存.aidl文件到工程的src/目录,然后build工程,SDK tools会在工程的/gen目录生成IBinder interface。生成的文件名与.aidl文件名相同,但是扩展名为.java,如IRemoteService.aidl编译生成IRemoteService.java。
2、实现接口
当你编译你的APP,Android SDK tools会根据.aidl文件生成.java接口文件。生成的接口包括一个名为Stub的子类,是它的父接口的一种抽象的实现(例如,YourInterface.Stub),并且申明了.aidl文件中的所有方法。继承生成的Binder接口(如YourInterface.Stub),并且实现从.aidl文件继承的方法。
下面的例子用匿名实例的方式实现了一个叫IRemoteService的接口,这个接口由IRemoteService.aidl文件定义。
private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
@Override
public int plus(int a, int b) throws RemoteException {
return a + b;
}
};
现在mBinder是Stub类的一个实例,它定义了Service的RPC接口。在下一步中,将这个实例暴露给Clients,这样Client就可以与Service交互。实现AIDL接口时,需要遵循下面的规则:
•对接口的调用可能在未知线程上执行,因此您需要从一开始就考虑多线程,并正确地将您的Service构建为线程安全的。
•默认情况下,RPC调用是同步的。如果您知道Service需要超过几毫秒才能完成一个请求,那么您不应该从Activity的主线程调用它,因为这样可能导致APP出现ANR,所以您应该从Client的一个单独线程去调用它。
•Service抛出的异常不会被返回给调用者。
3、把接口开放给Client
一旦您实现了Service的接口,就需要将其公开给Client,这样Client就可以绑定到Service了。开放接口需要继承Service,实现onBind()且返回一个Stub类的实例。下面例子中Service把IRemoteService接口开放给Client:
public class RemoteService extends Service {
private static final String TAG = "RemoteService";
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
@Override
public int plus(int a, int b) throws RemoteException {
return a + b;
}
};
}
现在,当一个Client(如Activity)调用bindService()连接到该Service,Client的onServiceConnected()回调就会接收到Service的onBind()方法返回的mBinder实例,然后Client调用YourServiceInterface.Stub.asInterface(service)把IBinder映射为YourServiceInterface类型。
Client端实现
下面是Client调用远程AIDL接口的步骤:
1、 把.aidl文件拷贝到Client的src/目录下。
2、 申明一个IBinder接口的实例。
private IRemoteService mRemoteService;
3、 实现ServiceConnection。
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Toast.makeText(mContext, "onServiceConnected", Toast.LENGTH_SHORT).show();
mRemoteService = IRemoteService.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Toast.makeText(mContext, "onServiceDisconnected", Toast.LENGTH_SHORT).show();
}
};
4、 调用Context.bindService(),并且传入ServiceConnection的实例。
Intent intent = new Intent();
intent.setAction("com.jenny.example.service.service.RemoteService");
intent.setPackage("com.jenny.example.service");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
5、 在onServiceConnected()中,收到IBinder实例。调用YourInterfaceName.Stub.asInterface((IBinder)service),把IBinder转换为YourInterface类型。
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Toast.makeText(mContext, "onServiceConnected", Toast.LENGTH_SHORT).show();
mRemoteService = IRemoteService.Stub.asInterface(iBinder);
}
6、 调用接口定义的方法。
try {
int result = mRemoteService.plus(1, 3);
Log.i(TAG, "plus result="+result);
Toast.makeText(mContext, "plus result="+result, Toast.LENGTH_SHORT).show();
}catch (RemoteException e) {
Log.e(TAG, "RemoteException, "+e.getMessage());
}
7、 用Context.unbindService()断开连接。
unbindService(mConnection);
关于调用IPC Service的几点建议:
•对象是跨进程引用计数的。
•可以将匿名对象作为方法参数发送。
通过IPC传递对象
您可以通过IPC接口把一个类从一个进程传递到另一个进程。然而,这个类必须支持Parcelable接口,因为它允许Android系统将对象分解成可以跨越进程的原语。创建一个支持Parcelable协议的类的步骤如下:
1、 让你的类实现Parcelable接口。
2、 实现writeToParcel方法,把当前对象的状态写入Parcel。
3、 在你的类中添加一个叫CREATOR的static字段,并实现Parcelable.Creator接口。
4、 最后,创建一个aidl文件声明您的Parcelable类(如下Rect.aidl)。
如下是Rect.aidl文件:
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
如下是实现Parcelable类的Rect.java
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, int flags) {
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();
}
public int describeContents() {
return 0;
}
}
要注意从其他进程读取数据的安全限制。在此例子中,Rect从Parcel读取4个值,但是需要确保这些值在可接受的范围值内。有关如何使应用程序免受恶意软件攻击的更多信息,请参见 Security and Permissions。
AIDL接口的线程安全
调用AIDL接口是直接调用的函数,不能确定接口的实现是在哪个线程中进行的,Client可能同时发起多个调用,所以接口的实现必须要是线程安全的。如RemoteService实现了一个plus函数供Client调用:
private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
@Override
public int plus(int a, int b) throws RemoteException {
Log.i(TAG, "threadId="+Thread.currentThread().getId()+", threadName="+Thread.currentThread().getName());
return a + b;
}
};
Client为一个Activity,点击界面中的plus按钮,调用RemoteService的函数:
public void onClick(View view) {
switch (view.getId()) {
case R.id.plus_btn:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Log.i(TAG, "threadId="+Thread.currentThread().getId()+", threadName="+Thread.currentThread().getName());
int result = mRemoteService.plus(1, 3);
Log.i(TAG, "plus result="+result);
}catch (RemoteException e) {
Log.e(TAG, "RemoteException, "+e.getMessage());
}
}
});
thread.start();
break;
}
}
1、 如果Client和Service在同一进程中,那么接口的调用与实现在同一个线程中。例如, BindRemoteServiceActivity是Client,和RemoteService都运行在Service的进程中,点击BindRemoteServiceActivity中的plus按钮打印出如下日志:
2、 如果Client和Service不在同一进程中,那么接口的调用在Client进程中,接口的实现运行在Service的进程中,且在系统维护的线程池创建的线程中。例如,ClientMainActivity是Client,和RemoteService运行在不同进程中,点击ClientMainActivity中的plus按钮打印出如下日志:
示例代码
本文档所有示例代码下载路径:http://download.youkuaiyun.com/download/jennyliliyang/10155100