Android IPC机制(四):细说Binder连接池

作者: 丶蓝天白云梦 | 来源:发表于2016-04-14 19:04 被阅读913次

    一、 前言

    在上一篇文章Android IPC机制(三):浅谈Binder的使用中,笔者讲述了Binder的使用及其工作机制,利用AIDL方式能很方便地进行客户端和服务端的跨进程通信。
      但是,我们想一下,如果按照我们之前的使用方法,必须满足一个AIDL接口对应一个service,那么问题来了,假如我们的应用,有很多模块,而每一个模块都需要和服务端通讯,那么我们也要为每一个模块创建特定的aidl文件,那么服务端service也会产生很多个,显然,如果aidl接口变多,那么service也会跟着变多,那么这样的用户体验就会非常不好,那么我们该怎么做呢?在任玉刚著的《Android 开发艺术探索》一书中,给出了一个Binder连接池的概念,即利用一个Binder连接池来管理所有Binder,服务端只需要管理这个Bindere连接池即可,这样就能实现一个service管理多个Binder,为不同的模块返回不同的Binder,以实现进程间通讯。所以,本文将讲述如何实现Binder连接池。

    二、实现

    1、先提供两个AIDL接口来模拟多个模块都要使用AIDL的情况:
    ISpeak接口和ICalculate接口:

    package com.chenyu.service;
    interface ISpeak {
        void speak();
    }
    
    package com.chenyu.service;
    interface ICalculate {
        int add(in int num1,in int num2);
    }
    

    接着,实现这两个接口:分别为Speak.java和Calculate.java文件:

    public class Speak extends Stub {
        public Speak() {
            Log.d("cylog","我被实例化了..............");
        }
        @Override
        public void speak() throws RemoteException {
            int pid = android.os.Process.myPid();
            Log.d("cylog","当前进程ID为:"+pid+"-----"+"这里收到了客户端的speak请求");
        }
    }
    
    public class Calculate extends ICalculate.Stub {
        @Override
        public int add(int num1, int num2) throws RemoteException {
            int pid = android.os.Process.myPid();
            Log.d("cylog", "当前进程ID为:"+pid+"----"+"这里收到了客户端的Calculate请求");
            return num1+num2;
        }
    }
    

    可以看到,这两个接口的实现类,都是继承了Interface.Stub类,这个在上一章的服务端代码出现过,是在服务端的service内部实现了接口的方法,而这里我们把实现了接口的方法从服务端抽离出来了,其实这个实现类依然是运行在服务端的进程中,从而实现了AIDL接口和服务端的解耦合工作,让服务端不再直接参与AIDL接口方法的实现工作。
      那么,服务端通过什么桥梁与AIDL接口联系呢?答案就是Binder连接池。Binder连接池管理着所有的AIDL接口,就如一位将军统帅着千军。客户端需要什么Binder,就提供信息给Binder连接池,而连接池根据相应信息返回正确的Binder,这样客户端就能执行特定的操作了。可以说,Binder连接池的思路,非常类似设计模式之中的工厂模式。接下来我们看Binder连接池的具体实现:

    2、为Binder连接池创建AIDL接口:IBinderPool.aidl:

    interface IBinderPool {
        IBinder queryBinder(int binderCode);  //查找特定Binder的方法
    }
    

    为什么需要这个接口?我们从上面的分析可以知道,service端并不直接提供具体的Binder,那么客户端和服务端连接的时候就应该返回一个IBinderPool对象,让客户端拿到这个IBinderPool的实例,然后由客户端决定应该用哪个Binder。所以服务端的代码很简单,只需要返回IBinderPool对象即可:

    3、服务端service代码:

    public class BinderPoolService extends Service {
    
        private Binder mBinderPool = new BinderPool.BinderPoolImpl();   // 1
        private int pid = Process.myPid();
        @Override
        public IBinder onBind(Intent intent) {
            Log.d("cylog", "当前进程ID为:"+pid+"----"+"客户端与服务端连接成功,服务端返回BinderPool.BinderPoolImpl 对象");
            return mBinderPool;
        }
    }
    

    ①号代码处,实例化了一个BinderPool.BinderPoolImpl类,并在onBind方法返回了这个mBinderPool对象。

    4、接下来我们看BinderPool的具体实现,代码比较长,我们先大体上认识,再详细分析:

    public class BinderPool {
        public static final int BINDER_SPEAK = 0;
        public static final int BINDER_CALCULATE = 1;
    
        private Context mContext;
        private IBinderPool mBinderPool;
        private static volatile BinderPool sInstance;
        private CountDownLatch mConnectBinderPoolCountDownLatch;
    
        private BinderPool(Context context) {               // 1
            mContext = context.getApplicationContext();
            connectBinderPoolService();
        }
    
        public static BinderPool getInstance(Context context) {     // 2
            if (sInstance == null) {
                synchronized (BinderPool.class) {
                    if (sInstance == null) {
                        sInstance = new BinderPool(context);
                    }
                }
            }
            return sInstance;
        }
    
        private synchronized void connectBinderPoolService() {      // 3
            mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
            Intent service = new Intent(mContext, BinderPoolService.class);
            mContext.bindService(service, mBinderPoolConnection,
                    Context.BIND_AUTO_CREATE);
            try {
                mConnectBinderPoolCountDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public IBinder queryBinder(int binderCode) {          // 4
            IBinder binder = null;
            try {
                if (mBinderPool != null) {
                    binder = mBinderPool.queryBinder(binderCode);
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            return binder;
        }
    
        private ServiceConnection mBinderPoolConnection = new ServiceConnection() {   // 5
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                
            }
    
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mBinderPool = IBinderPool.Stub.asInterface(service);
                try {
                    mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mConnectBinderPoolCountDownLatch.countDown();
            }
        };
    
        private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {    // 6
            @Override
            public void binderDied() {
                mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
                mBinderPool = null;
                connectBinderPoolService();
            }
        };
    
        public static class BinderPoolImpl extends IBinderPool.Stub {     // 7
    
            public BinderPoolImpl() {
                super();
            }
    
            @Override
            public IBinder queryBinder(int binderCode) throws RemoteException {
                IBinder binder = null;
                switch (binderCode) {
                    case BINDER_SPEAK: {
                        binder = new Speak();
                        break;
                    }
                    case BINDER_CALCULATE: {
                        binder = new Calculate();
                        break;
                    }
                    default:
                        break;
                }
    
                return binder;
            }
        }
    
    }
    

    大体上看,这个类完成的功能有实现客户端和服务端的连接,同时内有还有一个静态内部类:BinderPoolImpl,继承了IBinderPool.Stub,这也非常眼熟,所以这个静态内部类应该是运行了服务端的。好了,我们从上往下分析每一个方法的作用:
    ①private BinderPool(Context context)构造方法:这里传递了context对象,注意到,这个构造方法使用了private修饰,那么外界是无法直接调用构造器的,所以有了②号方法。

    ②public static BinderPool getInstance(Context context):看到getInstance字样,熟悉设计模式的读者应该知道了这里是使用了单例模式,而且是线程同步的懒汉式单例模式,在方法内部,把传递进来的context上下文参数传递进构造函数,即此时调用了①号方法,接着①号方法调用connectBinderPoolService()方法,即③号方法。

    ③private synchronized void connectBinderPoolService():这个方法主要用于客户端与服务端建立连接,在方法内部出现了CountDownLatch类,这个类是用于线程同步的,由于bindService()是异步操作,所以如果要确保客户端在执行其他操作之前已经绑定好服务端,就应该先实现线程同步。
      这里简单提一下这个类:
      CountDownLatch类有三个主要方法:
       (1)构造方法 CountDownLatch(int num):这里传递一个num值,为countdownlatch内部的计时器赋值。
       (2)countdown():每当调用一次这个方法,countdownlatch实例内部计时器数值 - 1。
       (3)await():让当前线程等待,如果内部计时器变为0,那么唤醒当前线程。

    ④public IBinder queryBinder(int binderCode):根据具体的binderCode值来获得某个特定的Binder,并返回。

    ⑤private ServiceConnection mBinderPoolConnection = new ServiceConnection(){} :这个类似于上一章客户端的连接代码,在服务端与客户端连接成功的时候,会回调当前的onServiceConnected()函数,我们来着重看看这个函数:

    @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mBinderPool = IBinderPool.Stub.asInterface(service);
                try {
                    mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mConnectBinderPoolCountDownLatch.countDown();
            }
    

    注意到,方法内部执行了mBinderPool=IBinderPool.Stub.asInterface(service)方法,由上一章的分析可知,这里的mBinderPool实际上是IBinderPool的一个代理对象,即此时客户端获得了服务端Binder连接池的一个代理对象。接着,最后执行了mConnectBinderPoolCountDownLatch.countDown()方法,此时,执行bindService()的线程就会被唤醒。

    ⑥private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient(){}:为IBinder设置死亡监听,如果连接意外中断,会自动重新连接。

    ⑦public static class BinderPoolImpl extends IBinderPool.Stub{} :这个类实现了IBinderPool.Stub,内部实现了IBinderPool的接口方法,这个实现类运行在服务端。内部是queryBinder()方法的实现,根据不同的Bindercode值来实例化不同的实现类,比如Speak或者Calculate,并作为Binder返回给客户端。当客户端调用这个方法的时候,实际上已经是进行了一次AIDL方式的跨进程通信。

    5、分析完BinderPool代码,最后,我们实现客户端代码:

    package com.chenyu.binderpool;
    import android.app.Activity;
    import android.os.*;
    import android.util.Log;
    
    import com.chenyu.service.BinderPool;
    import com.chenyu.service.Calculate;
    import com.chenyu.service.ICalculate;
    import com.chenyu.service.ISpeak;
    import com.chenyu.service.Speak;
    
    public class MainActivity extends Activity {
    
        private ISpeak mSpeak;
        private ICalculate mCalculate;
        private int pid = android.os.Process.myPid();
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    startWork();
                }
            }).start();
        }
    
        private void startWork() {
            Log.d("cylog","当前进程ID为:"+pid);
            Log.d("cylog","获取BinderPool对象............");
            BinderPool binderPool = BinderPool.getInsance(MainActivity.this);     // 1
            Log.d("cylog","获取speakBinder对象...........");
            IBinder speakBinder = binderPool.queryBinder(BinderPool.BINDER_SPEAK);  // 2
            Log.d("cylog","获取speak的代理对象............");
            mSpeak = (ISpeak) ISpeak.Stub.asInterface(speakBinder);    // 3 
            try {
                mSpeak.speak();     // 4
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.d("cylog","获取calculateBinder对象...........");
            IBinder calculateBinder = binderPool.queryBinder(BinderPool.BINDER_CALCULATE);
            Log.d("cylog","获取calculate的代理对象............");
            mCalculate = (ICalculate) ICalculate.Stub.asInterface(calculateBinder);
            try {
                Log.d("cylog",""+mCalculate.add(5,6));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    由于跨进程通信是耗时操作,这里利用了子线程来进行绑定以及请求等操作。这里简单分析一下从子线程开始,整个跨进程通讯流程是怎样的:
      首先,在①处,调用了BinderPool的getInstance()方法,在里面执行了绑定服务的操作,此时得到的binderPool是BinderPool对象。接着执行②号代码,调用BinderPool对象的queryBinder()方法,此时发生了AIDL跨进程请求,得到服务端返回的特定的IBinder对象。接着执行③号代码,调用ISpeak.Stub.asInterface(IBinder iBinder)方法,把刚才获得的IBinder对象传递进去,此时返回了Speak的Proxy代理对象。 最后执行④号代码,调用代理对象mSpeak的speak()方法,此时再次发生了AIDL跨进程请求,调用了服务端Speak类的speak方法。
      我们看一下运行结果:

    运行结果

    三、总结

    最后总结一下使用Binder连接池的流程:
    (1)为每个业务模块创建AIDL接口,以及实现其接口的方法。
    (2)创建IBinderPool.aidl文件,定义queryBinder(int BinderCode)方法,客户端通过调用该方法返回特定的Binder对象。
    (3)创建BinderPoolService服务端,在onBind方法返回实例化的BinderPool.IBinderPoolImpl对象。
    (4)创建BinderPool类,在该类实现客户端与服务端的连接,解决线程同步的问题,设置Binder的死亡代理等。在onServiceConnected()方法内,获取到IBinderPool的代理对象。此外,IBinderPool的实现类:IBinderPoolImpl是BinderPool的内部类,实现了IBinderPool.aidl的方法:queryBinder()。

    相关文章

      网友评论

      • Guxx:楼主能给下你的demo,或者目录吗?
        BinderPool是写在服务端的吧,那客户端的Activity怎么引用它呀
      • Allens_Jiang:兄弟,你最后的这个目录????怎么设置的????
      • ea135cb3aa51:这是在同一个app内部实现的代码吧?
        丶蓝天白云梦:@思念叨火车 该例子是同一app的,但是运行在不同的进程中

      本文标题:Android IPC机制(四):细说Binder连接池

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