【Android】Binder连接池

本文代码:Github

先说说问题吧,AIDL需要一个客户端和一个服务端,服务端往往是一个service,但是这样就会有问题,当团队多了,模块多了,每个模块自己一个service,显然这样是很坑爹的。所以,引入Binder连接池。

一、实现思路

《【Android】Binder连接池》 Binder连接池原理.png

对于每个AIDL接口,分别实现对应的Binder,统一一个service,然后每次bind service的时候,通过一个连接池来进行Binder分发,queryBinder里面通过请求的code来决定分配哪个Binder。这样,就可以避免随着项目模块的增多,service变多,每次有新模块接口增加的时候,只需要在queryBinder里的switch新加一个case判断即可,且能实现模块间的解耦和service与模块具体实现之间的解耦,一举两得。

二、从零开始实现Binder连接池

创建两个AIDL。

// IUser.aidl
package com.cm.mybinderpool;

interface IUser {
    boolean login(String username, String password);
}

// ICompute.aidl
package com.cm.mybinderpool;

interface ICompute {
    int add(int x, int y);
}

然后分别实现对应的Binder。

public class UserImpl extends IUser.Stub {
    @Override
    public boolean login(String username, String password) throws RemoteException {
        return true;
    }
}

public class ComputeImpl extends ICompute.Stub {
    @Override
    public int add(int x, int y) throws RemoteException {
        return x + y;
    }
}

现在还比较简单。接下来就是我们的BinderPool,先新建一个AIDL接口,里面只有一个queryBinder方法。

// IBinderPool.aidl
package com.cm.mybinderpool;

interface IBinderPool {
    IBinder queryBinder(int binderCode);
}

实现BinderPool。
首先,既然是个线程池,就应该是个单例模式。这里采用双重检查锁实现。单例获取的时候需要传入上下文context,主要是之后bind服务的时候,需要用到上下问信息。

private static volatile BinderPool sInstance;
private Context mContext;

private BinderPool(Context context) {
    mContext = context;
}

public static BinderPool getInstance(Context context) {
    if (sInstance == null) {
        synchronized (BinderPool.class) {
            if(sInstance == null) {
                sInstance = new BinderPool(context);
            }
        }
    }
    return sInstance;
}

实现binder分发,即IBinderPool的queryBinder接口,实现比较简单,就是switch-case判断。

//binder code
public static final int BINDER_USER = 0;
public static final int BINDER_COMPUTE = 1;

public static class BinderPoolImpl extends IBinderPool.Stub {
    @Override
    public IBinder queryBinder(int binderCode) throws RemoteException {
        switch (binderCode) {
            case BINDER_COMPUTE:
                return new ComputeImpl();
            case BINDER_USER:
                return new UserImpl();
            default:
                return null;
        }
    }
}

好了,接下来要创建对应的service了。这里返回IBinderPool的Binder。

public class BinderPoolService extends Service {
    public BinderPoolService() {
    }

    Binder binderPool = new BinderPool.BinderPoolImpl();

    @Override
    public IBinder onBind(Intent intent) {
        return binderPool;
    }
}

线程池作用除了进行binder的分发外,还有就是service的连接。
连接是在初始化的时候进行。

private BinderPool(Context context) {
    mContext = context;
    connectService();
}

connectService即完成连接。
这里会用到Java的CountDownLatch,CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量,每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,其他线程完成后通知主线程,通过CountDownLatch.countDown()来使得值减1,因为binderservice回调之后主线程才能继续,所以这里用CountDownLatch来实现。

private CountDownLatch mConnectBinderPoolCountDownLatch;
private IBinderPool mBinderPool;

private void connectService() {
    mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
    Intent intent = new Intent(mContext, BinderPoolService.class);
    mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
    try {
        mConnectBinderPoolCountDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        mBinderPool = IBinderPool.Stub.asInterface(iBinder);
        mConnectBinderPoolCountDownLatch.countDown();
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {

    }
};

这里再为Binder设置一下死亡监听。在服务连接成功之后,得到binder,利用iBinder.linkToDeath(mBinderPoolDeathRecipient, 0),当连接池发现服务断开的时候,需要重新去连接服务,保持长连接。

private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
        mBinderPool = null;
        connectService();
    }
};

然后,我们要在BinderPool中提供最重要的分发接口出去。

public IBinder queryBinder(int code) {
    if(mBinderPool == null) {
        return null;
    }
    try {
        return mBinderPool.queryBinder(code);
    } catch (RemoteException e){
        e.printStackTrace();
    }
    return null;
}

这里去掉用了service的queryBinder方法,然后具体实现还是在BinderPool当中,这样使用过程全部和BinderPool类打交道而不和service打交道了。

好了,这样就实现了我们的Binder连接池了,现在我们来使用一下它。
在我们的MainActivity中,调用的时候是一个耗时操作,所以需要另开一个线程进行,否则会阻塞UI线程,造成ANR。怎么开线程就不说了,直接线程具体调用的方法。

private void testBinderPool() {
    BinderPool mBinderPool = BinderPool.getInstance(MainActivity.this);
    //测试ICompute
    IBinder mComputeBinder = mBinderPool.queryBinder(BinderPool.BINDER_COMPUTE);
    ICompute mCompute = ICompute.Stub.asInterface(mComputeBinder);
    try {
        Log.i("chenming", "1+2 = " + mCompute.add(1, 2));
    } catch (RemoteException e) {
        e.printStackTrace();
    }
    
    //测试IUser
    IBinder mUserBinder = mBinderPool.queryBinder(BinderPool.BINDER_USER);
    IUser mUser = IUser.Stub.asInterface(mUserBinder);
    try {
        Log.i("chenming", "login " + mUser.login("user", "psd"));
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

运行一下,可以看见运行结果log。

05-30 16:38:28.964 32108 32132 I chenming: 1+2 = 3
05-30 16:38:28.965 32108 32132 I chenming: login true

三、回顾

整个过程其实客户端都是和BinderPool进行打交道的,BinderPool是个单例,主要是由于访问过程是个并发的过程,如果有两个BinderPool实例的话会出现非常多不可控的问题。BinderPool与Service打交道,BinderPool给客户端其实只提供了两个接口,一个是getInstance用以获取实例,一个是queryBinder进行binder分发。

getInstance的时候,如果实例还未初始化,则马上new一个实例,同时也开始了service的连接,连接service之后,保持连接状态,所以需要去监听Binder的死亡。

连接成功后,获取到对应IBinderPool的Binder实例。这个Binder类的具体实现还是在BinderPool当中。

下次要新模块添加AIDL的时候就很简单了,修改BinderPool里面增加一个code,然后在queryBinder中添加一个case分路进行实例化即可。

    原文作者:黑暗终将过去
    原文地址: https://www.jianshu.com/p/3ca35499c630
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞