Android multi-process series

  • Several basic problems of Android multi-process communication
  • Binder for Android multiprocess
  • Android multiprocess manual Binder class
  • Android Multiprocess Messenger
Next articleBinder: Android Multiprocess Manual BinderWith the extension of Binder class, we also need to transform the corresponding server and client

Client side and server side transformation

Server Transformation
  • Added the ability to register listening interfaces
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<IOnNewBookArrivedListener>();

private Binder mBinder = new BookManagerImpl(){
    @Override
    public List<Book> getBookList() throws RemoteException {
        Log.e(TAG, "getBookList-->"+ System.currentTimeMillis());
        return mBookList;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
        Log.e(TAG, "addBook-->");
        mBookList.add(book);
    }

    @Override
    public void registerListener(IOnNewBookArrivedListener listener) {
        if(! mListenerList.contains(listener)) { mListenerList.add(listener); }else {
            Log.e(TAG, "already exists");
        }
        Log.e(TAG, "registerListener, size:"+mListenerList.size());
    }

    @Override
    public void unRegisterListener(IOnNewBookArrivedListener listener) {
        if (mListenerList.contains(listener)) {
            mListenerList.remove(listener);
            Log.e(TAG, "unRegisterListener listener succeed");
        }else {
            Log.e(TAG, "not found, can not unregister");
        }
        Log.e(TAG, "unRegisterListener, current size:"+mListenerList.size()); }};Copy the code
  • Add a task that periodically adds a book to the list of books and triggers a notification client operation
@Override
public void onCreate() {
    super.onCreate();
    Log.e(TAG, "onCreate-->"+ System.currentTimeMillis());
    mBookList.add(new Book(1, "Android"));
    mBookList.add(new Book(2, "IOS"));
    new Thread(new ServiceWorker()).start();
}

private void onNewBookArrived(Book book) throws RemoteException{
    mBookList.add(book);
    Log.e(TAG, "new book arrived, notify listeners:" + mListenerList.size());
    for (int i=0; i<mListenerList.size(); i++) {
        IOnNewBookArrivedListener listener = mListenerList.get(i);
        Log.e(TAG, "new book arrived, notify listener:" + listener);
        listener.onNewBookArrived(book);
    }
}

private class ServiceWorker implements Runnable {

    @Override
    public void run() {
        while(! mIsServiceDestoryed.get()) { try { Thread.sleep(5000); }catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId,"new book#"+ bookId); try { onNewBookArrived(newBook); }catch (RemoteException e) { e.printStackTrace(); }}}}Copy the code
  • The process of notifying the client is to call the onNewBookArrived method of each registered listening interface, that is, a server calling the client, OnNewBookArrived –>onTransact–>Proxy onNewBookArrived–> client
Client Transformation
  • To declare a IOnNewBookArrivedListener object first, and register to the server
private IBookManager mRemoteBookManager;
private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        Log.e(TAG, "ServiceConnection-->"+ System.currentTimeMillis());
        IBookManager bookManager = BookManagerImpl.asInterface(iBinder);
        mRemoteBookManager = bookManager;
        try {
            List<Book> list = bookManager.getBookList();
            Log.e(TAG, "query book list, list type:" + list.getClass().getCanonicalName());
            Log.e(TAG, "query book list:" + list.toString());
            Book newBook = new Book(3, "Android advanced");
            bookManager.addBook(newBook);
            Log.e(TAG, "add book:" + newBook);
            List<Book> newList = bookManager.getBookList();
            Log.e(TAG, "query book list:" + newList.toString());
            bookManager.registerListener(mOnNewBookArrivedListener);

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        mRemoteBookManager = null;
        Log.e(TAG, "binder died"); }}; / * * * this method runs on the client side binder in the thread pool, not directly for UI operation * / private IOnNewBookArrivedListener mOnNewBookArrivedListener = newOnNewBookArrivedListenerImpl(){ @Override public void onNewBookArrived(Book book) { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget(); }};Copy the code
  • Then due to the client’s IOnNewBookArrivedListener callback methods onNewBookArrived running in the Binder in the thread pool, so need a Handler to switch to the UI thread
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message message) {
        switch (message.what) {
            case MESSAGE_NEW_BOOK_ARRIVED:
                Log.e(TAG, "receive new book:" + message.obj);
                break; default: super.handleMessage(message); }}};Copy the code
  • Finally, you need to unregister the server when the client exits
@Override
protected void onDestroy() {
    if(mRemoteBookManager ! = null && mRemoteBookManager.asBinder().isBinderAlive()) { try { Log.e(TAG,"unRegister listener:" + mOnNewBookArrivedListener);
            mRemoteBookManager.unRegisterListener(mOnNewBookArrivedListener);
        }catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    unbindService(mConnection);
    super.onDestroy();
}
Copy the code
results

  • As we can see from the figure above, we have successfully implemented the expected functionality and the server notifies the client of the invocation as we described above

  • Next, we exit the application so that we can test the functionality of untying listeners

  • As shown in the figure above, the server call failed to unbind, indicating that the interface could not be found.

Interprocess communication with Binder takes the parameters passed by the client, the AIDL interface, and the Parcelable object, and transforms it into a new object. Because objects cannot be transferred across processes, the transfer of objects across processes is essentially a serialization and deserialization process. Therefore, in the above case, the server does not have the object of the client, so it will certainly not find the untying failure. So what do we do?

RemoteCallBackList

What’s RemoteCallBackList
public class RemoteCallbackList<E extends IInterface> { /*package*/ ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); . }Copy the code
  • RemoteCallBackList has an internal Map structure that holds all AIDL callbacks. The Map’s key is of type IBinder and its value is of type CallBack
public boolean register(E callback, Object cookie) {
    synchronized (mCallbacks) {
        if (mKilled) {
            return false;
        }
        IBinder binder = callback.asBinder();
        try {
            Callback cb = new Callback(callback, cookie);
            binder.linkToDeath(cb, 0);
            mCallbacks.put(binder, cb);
            return true;
        } catch (RemoteException e) {
            return false; }}}Copy the code
  • Every time a new interface comes in, call the Register method to register it and add it to mCallbacks
  • As seen in the above source code, the underlying Binder objects are all the same, so that when we need to unregister the listener, Remove the listener by iterating through RemoteCallBackList to find the same listener as the Binder object on the unbind client
  • And we can see that thread synchronization is implemented in RemoteCallBackList, so we don’t have to deal with synchronization issues when we use it to register and unregister.
  • In particular, when the client process terminates, RemoteCallBackList automatically removes the listener registered by the client
Unbind registration with the RemoteCallBackList implementation
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
private Binder mBinder = new BookManagerImpl(){
    @Override
    public List<Book> getBookList() throws RemoteException {
        Log.e(TAG, "getBookList-->"+ System.currentTimeMillis());
        return mBookList;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
        Log.e(TAG, "addBook-->"); mBookList.add(book); } @ Override public void registerListener (IOnNewBookArrivedListener listener) {/ / registered interface mListenerList. Register (the listener); } @ Override public void unRegisterListener (IOnNewBookArrivedListener listener) {/ registration/solution interface mListenerList.unregister(listener); }}; Private void onNewBookArrived(Book Book) throws RemoteException{mbooklist.add (Book); // Notify the client. final int N = mListenerList.beginBroadcast();for (int i=0; i<N; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if(listener ! = null) { try { listener.onNewBookArrived(book); }catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); }Copy the code
  • Using RemoteCallBackList, we can successfully unregister the listener on the client
Pay attention to the point
  • RemoteCallBackList is not a List, and we cannot manipulate it like a List, for example by calling the size method
  • The RemoteCallBackList must be traversed as shown in the code above, and the beginBroadcast and finishBroadcast methods must be used together

Welcome to follow my wechat official number, and learn and grow together with me!Copy the code