本文例子中的源码地址: Github:进程间通信之AIDL
AIDL(Android 接口定义语言)是定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口,一个进程通常无法访问另一个进程的内存,但是可以通过AIDL进行进程间通信。AIDL可以让客户端以IPC(进程间通信)方式访问服务端,并且服务端可以处理多线程;如果不需要处理多线程,则可以使用Messenger类来实现接口;如果只是需要本地的Service,不需要IPC过程,则只需要通过实现一个Binder类就可以了。使用AIDL的前提必须了解绑定Service,Service为我们创建Binder驱动,Binder驱动是服务端与客户端通信的桥梁。AIDL通过我们写的.aidl文件,生成了一个接口,一个Stub类用于服务端,一个Proxy类用于客户端,不熟悉Service的可以看下官方文档:Service
定义AIDL接口步骤:
1.创建.aidl文件
在服务端的src/ 目录内创建.aidl类型的文件,如果客户端和服务端不在一个应用内,则需要将.aidl文件复制到客户端。
.aidl文件中可以定义一个或多个接口方法,方法参数和返回值可以是任意类型的,默认情况下,AIDL支持下面的几种类型:
- Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
- String
- CharSequence
- List (List中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或声明的可打包类型。 可选择将List用作“通用”类(如List<String>)。另一端实际接收的具体类始终是ArrayList,但生成的方法使用的是List接口)
- Map (Map中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或声明的可打包类型。 不支持通用 Map(如 Map<String,Integer>形式的 Map)。 另一端实际接收的具体类始终是HashMap,但生成的方法使用的是 Map接口)
注意:如果在.aidl文件中使用的是自定义对象,即使此类型文件和.aidl文件在同一个文件夹中,也必须使用import语句导入,并且必须实现Parcelable接口。另外,非原语类型的参数需要指示数据走向的方向标记,可以是 in(输入)、out(输出) 或 inout(输入输出),默认是in,一定要正确规定方向,因为编组参数的开销极大。
比如声明一个自定义对象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) {
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();
}
}
创建一个声明可打包类的 .aidl 文件,与声明的自定义对象同名,并且这两个同名的类和.aidl文件放在同一个包下,如 Rect.aidl:
package android.graphics;
//注意这里是小写的parcelable来声明
parcelable Rect;
上面把Rect类放在.aidl目录中时,编译会提示找不到这个类,因为Android Studio默认会去java目录下找,需要在build.gradle文件 android{ } 中间增加一段代码,让aidl目录里面的java文件也能被识别:
sourceSets{
main{
java.srcDirs=['src/main/java','src/main/aidl']
}
}
以下是一个 IRemoteService.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.实现接口
SDK会根据.aidl文件自动生成一个接口,接口中有一个名为Stub的内部抽象类,用于扩展Binder类并实现AIDL中的方法,服务端需要覆写Stub类并实现方法。此外,Stub类中还有一个asInterface()方法,该方法带IBinder(通常便是传给客户端 onServiceConnected()回调方法的参数)并返回存根接口实例。
以下是一个使用匿名实例实现名为 IRemoteService 的接口(由以上 IRemoteService.aidl 示例定义)的示例:
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
}
};
mBinder是 Stub类的一个实例(一个 Binder),用于定义服务的 RPC 接口。 在下一步中,将向客户端公开该实例,以便客户端能与服务进行交互。
在实现 AIDL 接口时应注意遵守以下这几个规则:
- 由于不能保证在主线程上执行传入调用,因此一开始就需要做好多线程处理准备,并将您的服务正确地编译为线程安全服务。
- 默认情况下,RPC 调用是同步调用。如果明知服务完成请求的时间不止几毫秒,就不应该从 Activity 的主线程调用服务,因为这样做可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 您通常应该从客户端内的单独线程调用服务。
- 引发的任何异常都不会回传给调用方。
3.向客户端公开接口
服务端实现Service并重写Onbind()以返回Stub类的实现,客户端就能与服务进行交互了。
以下是一个向客户端公开 IRemoteService 示例接口的服务示例:
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() {
public void onServiceConnected(ComponentName className, IBinder service) {
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
客户端执行IPC过程:
- 在项目 src/目录中加入 .aidl文件。
- 声明一个IBinder接口实例(基于 AIDL 生成)。
- 实现 ServiceConnection。
- 调用 Context.bindService(),以传入ServiceConnection实现。
- 在您的 onServiceConnected()实现中,将收到一个 IBinder实例(名为service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)
,以将返回的参数转换为 YourInterface 类型。 - 调用在接口上定义的方法。始终捕获 DeadObjectException异常,它们是在连接中断时引发的;这将是远程方法引发的唯一异常。
- 如需断开连接,请使用您的接口实例调用 Context.unbindService()。
上面说的是整个流程,下面来举个栗子,先看效果图:
aidl.gif
服务端代码
Apple.aidl:
package org.ninetripods.mq.multiprocess_sever;
parcelable Apple;
其中Apple类已经实现Parcelable接口了,这里就不再贴出来了,接着声明IAidlCallBack.aidl 提供客户端调用的方法:
// IAidlCallBack.aidl
package org.ninetripods.mq.multiprocess_sever;
// Declare any non-default types here with import statements
import org.ninetripods.mq.multiprocess_sever.Apple;
interface IAidlCallBack {
Apple getAppleInfo();
}
然后编写RemoteService.java类:
public class RemoteService extends Service {
public RemoteService() {
}
@Override
public IBinder onBind(Intent intent) {
//通过onBind()方法返回Binder实例供客户端调用
return mBinder;
}
private IAidlCallBack.Stub mBinder = new IAidlCallBack.Stub() {
@Override
public Apple getAppleInfo() throws RemoteException {
return new Apple("蛇果", 20f, getString(R.string.respose_info));
}
};
}
最后在AndroidManifest.xml里声明一下:
<service
android:name=".RemoteService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.mq.common.service" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
客户端代码:AidlActivity.java
首先实现ServiceConnection,在onServiceConnected()中通过IAidlCallBack.Stub.asInterface(service) 拿到Binder实例,然后就可以调用服务端方法了,代码如下:
private ServiceConnection mCommonConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isCommonBound = true;
mCommonService = IAidlCallBack.Stub.asInterface(service);
if (mCommonService != null) {
try {
Apple apple = mCommonService.getAppleInfo();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
isCommonBound = false;
mCommonService = null;
showMessage("\n连接远程服务端失败...");
}
};
最后是绑定服务:
Intent commonIntent = new Intent();
commonIntent.setAction("android.mq.common.service");
commonIntent.setPackage("org.ninetripods.mq.multiprocess_sever");
bindService(commonIntent, mCommonConnection, Context.BIND_AUTO_CREATE);
例子中还有个观察者模式,跟在同一进程中使用观察者模式是有区别的,要用到RemoteCallbackList,具体使用方法代码中有,就不再详述了~
恩~整个过程差不多是这样了,完整代码已上传至 Github:进程间通信之AIDL,如果对您有帮助,给个star吧,感激不尽~(注:项目中有两个项目,请确保先启动Server端,否则看不到效果)
网友评论