使用 AIDL 实现客户端和服务的进程间通信(IPC)

1. 使用步骤

AIDL 的意思是 Android 接口定义语言。利用它来定义进程间通信时双方认可的编程接口。

第一步:创建 .aidl 文件

AIDL 接口方法中支持的参数类型:

  • 8 个基本数据类型
  • String
  • CharSequence
  • List:List 中的所有元素必须是以上支持的数据类型、其他 AIDL 生成的接口或自定义的 Parcelable 类型。接收端实际接受的具体类始终是 ArrayList,但生成的方法使用 List 接口。
  • Map:Map 中元素要求和 List 一样。接收端实际接受的具体类始终是 HashMap,但生成的方法使用 Map 接口。

以上没有列出的每个附加的类型(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 <package_name>;
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();

AIDL 模块复用

安卓应用项目非常庞大时,假设有 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();
    }
}


参考资料

  1. Android开发艺术探索

猜你喜欢

转载自blog.csdn.net/weixin_40255793/article/details/81537377