Use of Android Binder

Posted by robviperx on Sat, 11 May 2019 00:13:42 +0200

1. Creation and parsing of AIDL files
Binder is still relatively simple to use. Create an IBinderPool.aidl file and clean it. You can generate a Java file for us.

// IBinderPool.aidl
package com.example.binder.aidl;
interface IBinderPool {
    IBinder queryBinder(int binderCode);
}

The generated Java files are as follows:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\AndroidDemo\\BinderDemo\\app\\src\\main\\aidl\\com\\example\\binder\\aidl\\IBinderPool.aidl
 */
package com.example.binder.aidl;
// Declare any non-default types here with import statements

public interface IBinderPool extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.example.binder.aidl.IBinderPool {
        //The unique identifier of a Binder is usually represented by the class name of the current Binder, such as com.example.binder.aidl.IBinderPool here.
        private static final java.lang.String DESCRIPTOR = "com.example.binder.aidl.IBinderPool";

        /**
         * Construct the stub at attach it to the interface.
         * 
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.example.binder.aidl.IBinderPool interface,
         * generating a proxy if needed.
         * Used to convert the Binder object on the server side into the AIDL interface type object needed by the client side. This type conversion process distinguishes the process. If the client and the server are in the same process, then this method returns the Stub object itself on the server side, otherwise it returns the Stub.proxy object encapsulated by the system.
         */
        public static com.example.binder.aidl.IBinderPool asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            //Query local interface
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            //If there is a direct return to the object (on behalf of the server and the client in the same process)
            if (((iin != null) && (iin instanceof com.example.binder.aidl.IBinderPool))) {
                return ((com.example.binder.aidl.IBinderPool) iin);
            }
//Returns the current Binder object
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        //This method runs in the server's Binder thread pool. When a client initiates a cross-process request, the remote request is encapsulated at the bottom of the system and handed over to it for processing.
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_queryBinder: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    android.os.IBinder _result = this.queryBinder(_arg0);
                    reply.writeNoException();
                    reply.writeStrongBinder(_result);
                    //Returning false will fail on behalf of the client request, which can be validated according to the characteristics.
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.example.binder.aidl.IBinderPool {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
            /**
             * This method runs on the server side.
             */
            @Override
            public android.os.IBinder queryBinder(int binderCode) throws android.os.RemoteException {
                //Create input type Parcel objects
                android.os.Parcel _data = android.os.Parcel.obtain();
                //Create output type Parcel objects
                android.os.Parcel _reply = android.os.Parcel.obtain();
                //Create a return value object
                android.os.IBinder _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //Write request parameters
                    _data.writeInt(binderCode);
                    //Initiate RPC (remote procedure call request) while the current thread hangs; then the onTransact method on the server side is invoked until the RPC process returns and the current thread continues to execute.
                    mRemote.transact(Stub.TRANSACTION_queryBinder, _data, _reply, 0);
                    _reply.readException();
                    //Return data from _reply
                    _result = _reply.readStrongBinder();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_queryBinder = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public android.os.IBinder queryBinder(int binderCode) throws android.os.RemoteException;
}

Note: First, when the client initiates a remote request, because the current thread will be suspended until the server process returns data, all remote methods can not initiate the remote request in the UI thread if they are time-consuming; second, because the Binder method on the server runs in the Binder thread pool, the Binder method should be synchronized regardless of time-consuming. The way to do it, because it's already running in a thread
2. Use of Binder
_The above implementation of AIDL file creation, then how to use Nissl? In fact, it is relatively simple to create a Service, return a server Binder object in its onBind, and get the Binder object in the client's ServiceConnection.

//Server side
public class BinderPoolService extends Service {
    private static final String TAG = "BinderPoolService";

    private Binder mBinderPool = new BinderPool.BinderPoolImpl();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }
}
//Client
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //Get the Binder interface returned from the server
            mBinderPool = IBinderPool.Stub.asInterface(service);
            ...
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
Intent intent = new Intent(mContext, BinderPoolService.class);
mContext.bindService(intent, mBinderPoolConnection, Context.BIND_AUTO_CREATE);

3. Implementation of Binder Pool
There is a Binder pool concept in Android Art Exploration, which means the same as thread pool and database connection pool. Avoid creating a service for each Binder, and get its corresponding Binder object in a service according to different business. This saves a lot of resources. After all, Service is also a system resource. The concrete realization is as follows:

//
public class BinderPoolService extends Service {


    private static final String TAG = "BinderPoolService";

    private Binder mBinderPool = new BinderPool.BinderPoolImpl();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }
}
//
public class BinderPool {

    private static final String TAG = "BinderPool";

    public static final int BINDER_NONE = -1;
    public static final int BINDER_COMPUTE = 0;
    public static final int BINDER_SECURITY_CENTER = 1;


    private Context mContext;
    private static volatile BinderPool mInstance;
    private CountDownLatch mConnectBinderPoolCountDownLatch;
    private IBinderPool mBinderPool;

    public BinderPool(Context context) {
        mContext = context.getApplicationContext();
        connectBinderPoolService();
    }

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

    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        //Open a service
        Intent intent = new Intent(mContext, BinderPoolService.class);
        mContext.bindService(intent, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
        try {
            //Blockage, no further execution
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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


    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //Get the Binder object returned by the server.
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.i(TAG, "binder die");
            //Method of callback when disconnected
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            //Reconnect
            connectBinderPoolService();
        }
    };

    public static class BinderPoolImpl extends IBinderPool.Stub {

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            //Return unused Binder s based on code
            switch (binderCode) {
                case BINDER_SECURITY_CENTER:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDER_COMPUTE:
                    binder = new ComputeImpl();
                    break;
            }
            return binder;
        }
    }

}

4, extension
Extension 1: Binder may accidentally die, which is often due to the unexpected stop of the server process, at which time the service needs to be reconnected. So how to monitor whether the server is dead? There are two ways:

Setting DeathRecipient listener for Binder will receive a callback from the binderDied method when Binder dies, and the remote service can be reconnected in the binderDied.

 private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.i(TAG, "binder die");
            //Method of callback when disconnected
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            //Reconnect
            connectBinderPoolService();
        }
    };
//Set up monitoring
            try {
               mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

Reconnect services in onService DisConnected

    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
           ...
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        //Service reconnection
        }
    };

These two methods are optional, but onService Disconnected is invoked in the UI thread and binderDied is invoked in the client's Binder thread pool.
Extension 2: Observer is often used in common development, so how to implement this design pattern among multiple processes? Simply, a collection is used to manage objects on the server side, and then registration and anti-registration can be done. The code is as follows:

//CopyOnWriteArrayList is thread synchronized
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();
 private Binder mBinder = new IBookManager.Stub() {
        ...
        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.add(listener);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.remove(listener);
        }
    };

The above code does not implement the observer
But is the above implementation correct? When testing, you will find that anti-registration is of no use. That is to say, the above implementation is wrong. Why? Because Binder will re-transform the object passed by the client and generate a new object. Although the same client object is used in the process of registration and anti-registration, it will produce two new objects when it is passed to the server through Binder. Because objects can't be transmitted across processes, the cross-process transmission of objects is essentially a deserialized process, which is why all custom objects in AIDL must implement parcelable.
So how to achieve Observer Nie across processes? You can use the RemoteCallbackList collection. RemoteCallbackList is a generic type that supports the management of arbitrary AIDL interfaces. The working principle is simple. There is a Map structure inside it that is used to store all AIDL callbacks. The key of this Map is IBinder type and the value is Callback type. When the client terminates, it can automatically remove the object related to the client and automatically realize the function of thread synchronization. Then replace the CopyOnWriteArrayList in the above code with the RemoteCallbackList, which is as follows:

  private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();

    private Binder mBinder = new IBookManager.Stub() {
        ...
        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
            int N = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
        }
    };

This implements a cross-process version of the observer. One thing to note when using RemoteCallbackList is that it cannot be manipulated like a List, although it has a List in its name, it is not a List. Traveling through the RemoteCallbackList must follow the following way, where beginBroadcast and finishBroadcast must be used in pairs, then only the number of elements in the RemoteCallbackList is obtained.

            int N = mListenerList.beginBroadcast();
            Log.i(TAG, "unregisterListener current size: " + N);
            mListenerList.finishBroadcast();

Topics: Android Java Database