AIDL 的意思是 Android 接口定义语言。利用它来定义进程间通信时双方认可的编程接口。
第一步:创建 .aidl 文件
AIDL 接口方法中支持的参数类型:
以上没有列出的每个附加的类型(AIDL、Parcelable)都要加入 import 语句,即使是在同一包中定义。
// IRemoteService.aidl
package com.mindle.androidtest;
// Declare any non-default types here with import statements
import com.mindle.androidtest.IRemoteServiceCallback;
interface IRemoteService {
void registerCallback(in IRemoteServiceCallback callback);
}
对于自定义的 Parcelable 对象,还要创建 MyParcelable.aidl 文件,然后在 AIDL 接口中引用该包。如下:
package com.android.demo.parcelable;
parcelable MyParcelable;
package ;
import com.android.demo.parcelable;
interface IDemo {
void demoMethod(in MyParcelable myParcelable);
}
除了基本数据类型,其他类型的参数必须标上方向:in, out, inout。
aidl接口只支持方法,不支持声明静态常量。
build 之后,在同一包下,系统自动生成同名的 .java 文件。
第二步:实现接口
以下采用匿名实例实现了自动生成的抽象类 ISecondary.Stub 中的方法,mSecondaryBinder 是 Stub 类的实例(一个 Binder),用于定义服务的远程通信接口。下一步中,需要向客户端公开该实例。
private final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() {
@Override
public int getPid() throws RemoteException {
return Process.myPid();
}
};
注意:默认情况下,客户端调用 mSecondaryBinder 的方法是同步调用,所以要考虑是否开启新的线程。
第三步:向客户端公开该接口
通过将 AIDL 包中的 .aidl
文件拷贝到客户端应用的 src/
目录,使得客户端能够访问这些接口。在服务的 onBind 方法中,返回 AIDL 的具体实现。
@Override
public IBinder onBind(Intent intent) {
// Return the interface
return mSecondaryBinder;
}
客户端绑定上服务后,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法返回的 mSecondaryBinder 实例,它必须调用 YourServiceInterface.Stub.asInterface(service)
以将返回的参数转换成 YourServiceInterface
类型。如下示例:
ISecondary mSecondaryService;
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mSecondaryService = ISecondary.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mSecondaryService = null;
}
};
之所以要通过 Stub.asInterface() 方法,是因为该方法中会判断客户和服务是否在同一个进程中。如果在同一进程的话,就使用 Stub 的方式创建接口实例,直接使用进程中的对象来调用方法。如果在不在同一进程,就会调用 Stub.Proxy() 类来构造实例,之后的方法调用需要走 transact 过程,交由服务来运行。
最后一步:调用 IPC 方法
获得服务传来的 AIDL 实例后,客户端就可以根据接口中声明的方法来调用该远程实例了,而不用考虑具体实现。
int pId = mSecondaryService.getPid();
问题:远程传递序列化对象时,对象都是副本,如何标记同一个客户端传递的对象?
假如现在有这样一个应用场景:在 AIDL 接口中,有一个注册监听器的接口,每个客户端都会通过此接口向服务注册一个监听器,等到监听事件发生时,服务调用监听器,通知客户端事件的发生。
在服务端,如果我们直接使用 List 集合来保存每个注册的监听器,那么问题来了,当客户端解注册监听器时,客户端上传的同一个监听其对象,经过跨进程序列化和反序列化之后,会成为堆中的两个不同的对象(类似于 clone 方法),在没有重写监听器的 equal 方法时,我们无法解注册该监听器。
在服务中,使用 RemoteCallbackList 来标记一个客户端。register() 方法将客户端传过来的 AIDL 监听器实例的 asBinder()
和该实例作为键值对添加到 ArrayMap 中,unregister()
删除监听器实例的 asBinder()
所在的键值对。而每个客户端传过来的 AIDL 监听器实例的 asBinder()
对象是同一个。
在遍历客户端传来的注册对象时,需要用到三个函数,使用示例如下:
final int N = mRemoteCallbackList.beginBroadcast();
for (int i = 0; i < N; i++) {
IClientListener listener = mRemoteCallbackList.getBroadcastItem(i);
// 处理 listener
}
mRemoteCallbackList.finishBroadcast();
安卓应用项目非常庞大时,假设有 10 个不同的业务模块都需要使用 AIDL 来进行 IPC,如何在一个 Service 中处理?
解决方案是客户端发送需要的业务的特征码给服务,服务返回相应的 Binder 对象给它们。
为了方便讲解,我们假设有 2 个模块,如下:
// com.mindle.binderpool.ICompute.aidl
interface ICompute {
int add(int a, int b);
}
// com.mindle.binderpool.ISecurityCenter.aidl
interface ISecurityCenter {
String encrypt(String content);
String decrypt(String password);
}
第一步:特征码定义
abstract class BinderPool {
public static final int BINDER_NONE = -1;
public static final int BINDER_COMPUTE = 0;
public static final int BINDER_SECURITY_CENTER = 1;
}
为了根据特征码得到需要 Binder 对象,我们在加一个模块作为管理者:
// com.mindle.binderpool.IBinderPool.aidl
interface IBinderPool {
IBinder queryBinder(int binderCode);
}
第二步:服务端根据特征码返回 Binder
服务在 onBind() 方法中,将 IBinderPool 模块的具体实现返回给客户端。客户端可以从 IBinderPool 接口方便地获得多个模块的 Binder 实例。
// com.mindle.adroidservicetest.BinderPoolService
public class BinderPoolService extends Service {
private Binder mBinderPool = new BinderPoolServer.BinderPoolImpl();
@Override
public IBinder onBind(Intent intent) {
return mBinderPool;
}
}
// com.mindle.adroidservicetest.BinderPoolServer
public class BinderPoolServer extends BinderPool {
public static class BinderPoolImpl extends IBinderPool.Stub {
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_COMPUTE:
binder = new ComputeImpl();
break;
case BINDER_SECURITY_CENTER:
binder = new SecurityCenterImpl();
break;
default:
break;
}
return binder;
}
}
static class SecurityCenterImpl extends ISecurityCenter.Stub {
@Override
public String encrypt(String content) throws RemoteException {
return content.toUpperCase();
}
@Override
public String decrypt(String password) throws RemoteException {
return password.toLowerCase();
}
}
static class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
}
第三步:客户端模块处理类 BinderPoolClient
这里使用单例模式来获得一个 BinderPoolClient 对象,用来从服务中获得需要的模块。
BinderPoolClient 构造时采用 CountDownLatch 来将绑定服务和回调 onServiceConnected()
函数的过程同步化,因此,构造过程建议在单独的线程中进行。构造时就绑定服务获得了 IBinderPool 实例,在 BinderPoolClient 的 queryBinder()
方法中,根据特征码来获取服务提供的模块的 Binder 实现实例,然后客户端就可以调用 AIDL 接口中的方法,来使用这些模块了。
public class BinderPoolClient extends BinderPool{
private static volatile BinderPoolClient sInstance;
public static final String PACKAGE = "com.mindle.adroidservicetest";
public static final String CLASS = "com.mindle.adroidservicetest.BinderPoolService";
private Context mContext;
private IBinderPool mBinderPool;
private CountDownLatch mCountDownLatch;
private BinderPoolClient(Context context) {
mContext = context;
connectBinderPoolService();
}
// standard single instance
public static BinderPoolClient getInstance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPoolClient(context);
}
}
}
return sInstance;
}
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
if (mBinderPool != null) {
try {
binder = mBinderPool.queryBinder(binderCode);
} catch (RemoteException e) {
e.printStackTrace();
}
}
return binder;
}
private synchronized void connectBinderPoolService() {
mCountDownLatch = new CountDownLatch(1);
Intent service = new Intent();
service.setComponent(new ComponentName(PACKAGE, CLASS));
mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
mCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBinderPool = null;
mCountDownLatch.countDown();
}
};
}
最后一步:客户端如何调用模块
客户端在初始化时,先在单独的线程中获得两个模块的实例:
private void init() {
new Thread() {
@Override
public void run() {
mSecurityCenter = getSecurityCenter();
mCompute = getCompute();
}
}.start();
}
private ISecurityCenter getSecurityCenter() {
BinderPoolClient client = BinderPoolClient.getInstance(BinderPoolActivity.this);
IBinder securityBinder = client.queryBinder(BinderPool.BINDER_SECURITY_CENTER);
return ISecurityCenter.Stub.asInterface(securityBinder);
}
private ICompute getCompute() {
BinderPoolClient client = BinderPoolClient.getInstance(BinderPoolActivity.this);
IBinder securityBinder = client.queryBinder(BinderPool.BINDER_COMPUTE);
return ICompute.Stub.asInterface(securityBinder);
}
使用之前,首先要检查模块实例是否加载完成,不为 null 时,就可以使用了。根据 AIDL 实现 IPC 的原理,当客户端调用 mCompute.add(6, 25) 方法时,6 和 25 会由 Binder 被传递到服务中,服务中的 ICompute 的具体实现类会执行 add(6, 25) 操作,再将计算结果返回给客户端。这是一个同步操作,所以可能会比较耗时。
private void testSecurity() {
if (mSecurityCenter == null) {
output.append("mSecurityCenter == null\n");
return;
}
String msg = "hello world";
output.append("send msg to service:" + msg + "\n");
try {
String password = mSecurityCenter.encrypt(msg);
output.append("service encrypte it to " + password);
output.append("\n");
output.append("service decrypt to " + mSecurityCenter.decrypt(password));
output.append("\n");
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void testAdd() {
if (mCompute == null) {
output.append("mCompute == null\n");
return;
}
try {
output.append(String.format("%d + %d = %d\n", 6, 25, mCompute.add(6, 25)));
} catch (RemoteException e) {
e.printStackTrace();
}
}