【Android】Binder连接池

作者: 黑暗终将过去 | 来源:发表于2018-05-30 16:49 被阅读57次

    本文代码:Github

    先说说问题吧,AIDL需要一个客户端和一个服务端,服务端往往是一个service,但是这样就会有问题,当团队多了,模块多了,每个模块自己一个service,显然这样是很坑爹的。所以,引入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分路进行实例化即可。

    相关文章

      网友评论

        本文标题:【Android】Binder连接池

        本文链接:https://www.haomeiwen.com/subject/uwetsftx.html