Article code: Github
Let's talk about the problem first. AIDL needs a client and a server. The server is often a service, but there will be problems. When there are more teams and more modules, each module has its own service. Obviously, this is very embarrassing. Therefore, Binder connection pool is introduced.
1, Realization idea
Binder connection pool principle png
For each AIDL interface, the corresponding Binder is implemented separately and a service is unified. Then, each time a bind service is used, the Binder is distributed through a connection pool. In queryBinder, the requested code is used to determine which Binder is allocated. In this way, you can avoid the increase of services with the increase of project modules. Each time a new module interface is added, you only need to add a new case judgment in the switch in queryBinder, and you can realize the decoupling between modules and the decoupling between services and module implementations, killing two birds with one stone.
2, Implement Binder connection pooling from scratch
Create two aidls.
// 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); }
Then implement the corresponding Binder respectively.
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; } }
It's still relatively simple. Next is our BinderPool. First, create an AIDL interface with only one queryBinder method.
// IBinderPool.aidl package com.cm.mybinderpool; interface IBinderPool { IBinder queryBinder(int binderCode); }
Implement BinderPool.
First, since it is a thread pool, it should be a singleton mode. Double check lock is adopted here. The context context needs to be passed in when the singleton is obtained, mainly when the bind service is used later.
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; }
Implement binder distribution, that is, the queryBinder interface of IBinderPool. The implementation is relatively simple, that is, switch case judgment.
//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; } } }
OK, next, create the corresponding service. The Binder of IBinderPool is returned here.
public class BinderPoolService extends Service { public BinderPoolService() { } Binder binderPool = new BinderPool.BinderPoolImpl(); @Override public IBinder onBind(Intent intent) { return binderPool; } }
In addition to distributing binder s, thread pools also serve as service connections.
The connection is made during initialization.
private BinderPool(Context context) { mContext = context; connectService(); }
connectService completes the connection.
Java's CountDownLatch is used here. CountDownLatch is implemented through a counter. The initial value of the counter is the number of threads. Every time a thread completes its task, the value of the counter will be reduced by 1. When the counter value reaches 0, it indicates that all threads have completed the task, and then the threads waiting on the lock can resume executing the task. The first interaction with CountDownLatch is that the main thread waits for other threads. The main thread must call CountDownLatch. Immediately after starting other threads Await() method. In this way, the operation of the main thread will be blocked on this method. After other threads complete, the main thread will be notified through CountDownLatch Countdown() to reduce the value by 1. Because the main thread can continue after the binderservice callback, CountDownLatch is used here.
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) { } };
Here's another death monitor for binder. After the service connection is successful, get the binder and use ibinder Linktodeath (mbinderpooldeathrecipient, 0). When the connection pool finds that the service is disconnected, it needs to reconnect to the service to maintain a long connection.
private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0); mBinderPool = null; connectService(); } };
Then, we will provide the most important distribution interface in BinderPool.
public IBinder queryBinder(int code) { if(mBinderPool == null) { return null; } try { return mBinderPool.queryBinder(code); } catch (RemoteException e){ e.printStackTrace(); } return null; }
Here, the queryBinder method using service is removed, and the specific implementation is still in BinderPool. In this way, the use process is all related to BinderPool class rather than service.
OK, so we have implemented our Binder connection pool. Now let's use it.
In our MainActivity, calling is a time-consuming operation, so we need to open another thread, otherwise it will block the UI thread and cause ANR. Don't say how to open the thread. Directly the method called by the thread.
private void testBinderPool() { BinderPool mBinderPool = BinderPool.getInstance(MainActivity.this); //Test 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(); } //Test 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(); } }
Run it and you can see the running result 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
3, Review
In the whole process, the client actually deals with BinderPool. BinderPool is a single instance, mainly because the access process is a concurrent process. If there are two BinderPool instances, there will be many uncontrollable problems. BinderPool deals with services. BinderPool only provides two interfaces to the client, one is getInstance to obtain instances, and the other is queryBinder to distribute binders.
During getInstance, if the instance has not been initialized, a new instance will be created immediately and the service connection will be started. After the service is connected, the connection state will be maintained, so it is necessary to listen to Binder's death.
After the connection is successful, get the Binder instance of the corresponding IBinderPool. The specific implementation of this Binder class is still in BinderPool.
The next time you want to add AIDL to a new module, it's very simple. Modify the BinderPool to add a code, and then add a case shunt in queryBinder for instantiation.