【Android】Binder连接池

本文代码: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分路进行实例化即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容