Android Cross-Process Communication-IPC Preliminary Exploration (3) - Using AIDL

Preliminary Exploration of IPC (3) - Using AIDL


This article will introduce AIDL communication.

Other articles in this series:

Android Cross-Process Communication-IPC Preliminary Exploration (1)

Android Cross-Process Communication-IPC Preliminary Exploration (2) - Using Messenger


1. New requirements for examples

  1. Looking back at the example in IPC Preliminary Exploration (1) , the server provides two functions for the client:

    1. addStudent(): Add new data to the server's List on the client side
    2. getStudentList(): Obtain the List data of the server from the client.
  2. Now we have new requirements:

    • Problem: When the client calls to addStudent()add a new piece of data, it needs to query again to get the latest data. This means that the client must actively initiate a query to the server to determine when the List has updated the data.
    • New requirements: Now we want to change this process, when the server adds new data, it will actively notify the client. This is the observer pattern.
    • Solution: The common callback interface method does not work here - because this is a cross-process, an AIDL interface needs to be provided to achieve the purpose of cross-process.
  3. Design Solutions

    • Design a callback interface IOnNewStudentAddedListenerfor the server to notify the client. Since it is cross-process, this interface uses AIDL, which is automatically generated by the system. The specific implementation is provided by the client, and the server will call this interface after completing the addition of Student.
    • for IStudentManageradding registration and deregistration methods. These two methods are implemented by the server and provided to the client to call.
    • The server needs to provide specific implementations of the registration and deregistration methods. After the client obtains the remote binder, it calls these two methods through the binder (where appropriate).
    • The client needs to provide IOnNewStudentAddedListenerthe specific implementation, and notify the server to add/delete this interface object through the registration/deregistration method.
    • In order to do the simulation test, we added a thread on the server side, which will add a new student every 5 seconds and notify the client.
  4. accomplish:

    1. Callback interface IOnNewStudentAddedListener:

      //IOnNewStudentAddedListener.aidl
      package com.dou.ipcsimple;
      import com.dou.ipcsimple.Student;
      
      interface IOnNewStudentAddedListener {
          void onNewStudentAdded(in Student student);
      }
    2. Added two new methods for IStudentManagerregistering and unregistering observers.

      package com.dou.ipcsimple;
      import com.dou.ipcsimple.Student;
      import com.dou.ipcsimple.OnNewStudentAddedListener;
      interface IStudentManager {
          void addStudent(in Student student);
          List<Student> getStudentList();
          //新增的接口,用于注册和注销观察者
          void registerListener(OnNewStudentAddedListener listener);
          void unregisterListener(OnNewStudentAddedListener listener);
      }
      
    3. Server StudentManagerServicemodification:

      • In Binder, the specific implementation of the two methods of registration/deregistration is provided:

        private CopyOnWriteArrayList<IOnStudentAddedListener> mListeners = new CopyOnWriteArrayList<>();
            private Binder binder = new IStudentManager.Stub() {
        
                @Override
                public void addStudent(Student student) throws RemoteException {
                    students.add(student);
                }
        
                @Override
                public List<Student> getStudentList() throws RemoteException {
                    return students;
                }
                //注册
                @Override
                public void registerListener(IOnStudentAddedListener listener) throws RemoteException {
                    if (mListeners.contains(listener)) {
                        mListeners.add(listener);
                    } else {
                        Log.d(TAG,"already exists.");
                    }
                    Log.d(TAG,"registerListener size:"+ mListeners.size());
                }
                //注销
                @Override
                public void unregisterListener(IOnStudentAddedListener listener) throws RemoteException {
                    if (mListeners.contains(listener)) {
                        mListeners.remove(listener);
                        Log.d(TAG,"unregister listener succeed.");
                    } else {
                        Log.w(TAG,"Warning:listener not found, cannot unregister.");
                    }
                    Log.d(TAG,"unregisterListener size:"+ mListeners.size());
                }
            };
      • In addition, we do a mock test to add a new Student every 5 seconds on the server:

            private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
            private class AddStudentWork implements Runnable {
                @Override
                public void run() {
                    while (!mIsServiceDestroyed.get()){
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        int studentID = students.size() + 1;
                        Student student = new Student(studentID, "newStu_" + studentID);
                        try {
                            noticeStudentAdded(student);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            //挨个儿通知各位观察者
            private void noticeStudentAdded(Student student) throws RemoteException {
                students.add(student);
                Log.d(TAG, "noticeStudentAdded notify . listeners counts:" + mListeners.size());
                for (int i = 0; i < mListeners.size(); i++) {
                    IOnStudentAddedListener listener = mListeners.get(i);
                    listener.onStudentAdded(student);
                }
            }
        
            @Override
            public void onDestroy() {
                mIsServiceDestroyed.set(true);
                super.onDestroy();
            }
    4. Client modification:

      • IOnNewStudentAddedListenerThe specific implementation, and the creation of this interface object:

            //这里提供了IOnNewStudentAddedListener的匿名实现,这个mOnStudentAdded将作为接口对象注册/注销到远程服务端。
            private IOnStudentAddedListener mOnStudentAdded = new IOnStudentAddedListener.Stub() {
        
                @Override
                public void onStudentAdded(Student student) throws RemoteException {
                    //这里我们给handler发送消息,后续操作将由handler处理。
                    handler.obtainMessage(MESSAGE_NEW_STUDENT_ADDED, student).sendToTarget();
                }
            };
            //在handler中处理消息,可以更新UI线程。
            private Handler handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case MESSAGE_NEW_STUDENT_ADDED:
                            Log.d(TAG, "receive new student:" + msg.obj);
                            break;
                        default:
                            super.handleMessage(msg);
                    }
                }
            };
        
      • Register/deregister where appropriate mOnStudentAdded:

            private IStudentManager remoteStudentManager;
        
            private ServiceConnection mServiceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    bindService = true;
                    IStudentManager studentManager = IStudentManager.Stub.asInterface(service);
                    try {
                        ...
                        ...
                        //注册
                        studentManager.registerListener(mOnStudentAdded);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
        
                @Override
                public void onServiceDisconnected(ComponentName name) {
                    remoteStudentManager = null;
                    Log.d(TAG, "binder died.");
                }
            };
        
            @Override
            protected void onDestroy() {
                if (remoteStudentManager != null && remoteStudentManager.asBinder().isBinderAlive()) {
                    Log.i(TAG, "unregister listener:" + mOnStudentAdded);
                    try {
                       //注销
                       remoteStudentManager.unregisterListener(mOnStudentAdded);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                if (bindService){
                    unbindService(mServiceConnection);
                }
                super.onDestroy();
            }
        
  5. The complete code can be obtained from Github IPCSimple

2. Analysis of running results after adding callback interface

  1. Run the test:

    • Client: can observe normal operation results:

      04-23 09:03:14.146 21025-21025/com.dou.ipcsimple D/IPCSimple: Client Request students:[[stuId:1001, name:Tom], [stuId:1002, name:Jerry]]

      04-23 09:03:14.149 21025-21025/com.dou.ipcsimple D/IPCSimple: Client Request students:[[stuId:1001, name:Tom], [stuId:1002, name:Jerry], [stuId:1003, name:Jack]]

      04-23 09:03:19.141 21025-21025/com.dou.ipcsimple D/IPCSimple: receive new student:[stuId:4, name:newStu_4]

      04-23 09:03:24.143 21025-21025/com.dou.ipcsimple D/IPCSimple: receive new student:[stuId:5, name:newStu_5]

      04-23 09:03:29.146 21025-21025/com.dou.ipcsimple D/IPCSimple: receive new student:[stuId:6, name:newStu_6]

      04-23 09:03:34.149 21025-21025/com.dou.ipcsimple D/IPCSimple: receive new student:[stuId:7, name:newStu_7]

    • Server: can accept registration normally. But when we click the back button to exit the Activity, onDestroythe logout method called has a bug:

      04-23 09:05:44.276 22645-22662/com.dou.ipcsimple:remote D/StudentService: noticeStudentAdded notify . listeners counts:1

      04-23 09:05:45.646 22645-22658/com.dou.ipcsimple:remote W/StudentService: Warning:listener not found, cannot unregister.

      04-23 09:05:45.647 22645-22658/com.dou.ipcsimple:remote D/StudentService: unregisterListener size:1

  2. Unregister failed: deserialization - every generated object is brand new.

    • Reason exploration: Obviously mListeners.contains(listener)returned false, the deregistered listenerand registered listenerobjects are not the same.
      • Here objects are passed through binder, and the essence of this pass is serialization and deserialization. Therefore, the listener to the client is passed to the server through registration and deregistration respectively, and the two listeners obtained by deserialization are two different objects - because each serialization creates a separate object.
    • Solution: use RemoteCallbackList.

      • RemoteCallbackListUsed internally ArrayMap<IBinder,Callback>to hold all AIDL callbacks. For the three listener objects in this example (one for the client and two for the server), their low-level Binders are all the same. Using RemoteCallbackListthis feature, you can fix the bug that was logged out above.
      • Use RemoteCallbackListoverrides CopyOnWriteArrayList<IOnStudentAddedListener>, then modify the implementation of the register/unregister methods:

        private RemoteCallbackList<IOnStudentAddedListener> mListeners = new RemoteCallbackList<>();
        // 注册
        mListeners.register(listener);
        // 注销
        mListeners.unregister(listener);

      And, modify the notification method (please pay attention to the traversal method):

      ```
      private void noticeStudentAdded(Student student) throws RemoteException {
              students.add(student);
              /** 旧版本
              Log.d(TAG, "noticeStudentAdded notify . listeners counts:" + mListeners.size());
              for (int i = 0; i < mListeners.size(); i++) {
                  IOnStudentAddedListener listener = mListeners.get(i);
                  listener.onStudentAdded(student);
              }
               //*/
              //RemoteCallbackList not-is a List...
              final int COUNT = mListeners.beginBroadcast();
              Log.d(TAG, "noticeStudentAdded notify . listeners counts:" + COUNT);
              for (int i = 0; i < COUNT; i++) {
                  IOnStudentAddedListener listener = mListeners.getBroadcastItem(i);
                  if (null != listener) {
                      listener.onStudentAdded(student);
                  }
              }
              mListeners.finishBroadcast();
      
          }
      ```
      

      Curiously took a look getBroadcastItem(), internally arrays are used to support this index traversal method:

      ```
      public E getBroadcastItem(int index) {
              return ((Callback)mActiveBroadcast[index]).mCallback;
          }
      ```
      
    • The running result after the repair: When the Activity is exited, the logout is successful:

      04-23 10:17:46.187 14105-14130/com.dou.ipcsimple:remote D/StudentService: noticeStudentAdded notify . listeners counts:1

      04-23 10:17:47.369 14105-14105/com.dou.ipcsimple:remote W/StudentService: Service onDestroy

      04-23 10:17:51.189 14105-14130/com.dou.ipcsimple:remote D/StudentService: noticeStudentAdded notify . listeners counts:0

    • All modifications can be found in the complete example code on Github. Annotated is the old version of the code, that is, the code that caused the logout to fail.

3. Review the Binder workflow

  1. Let's take a look at the working diagram of Binder:
    image

    • After the client initiates a remote request, it will be suspended. At this time, if the execution of the server method takes too much time, the client thread will be blocked. If the Client thread is the UI thread, it may cause ANR.
    • We can getStudentListadd a delay to the Service method to simulate a method that takes too long:

      public List<Student> getStudentList() throws RemoteException {
                          SystemClock.sleep(5000);
                  return students;
              }

      Then run and find ANR:
      write picture description here

    • onServiceConnectedAnd onServiceDisconnectedalso runs on the UI thread, so it might also cause an ANR. The above results are proof.

  2. To resolve blocking/ANR:

    • When the client calls the method of the remote service, the called method runs in the Binder thread pool of the server. Similarly, when the remote server calls the client's method, the called method runs in the client's Binder thread pool.
    • When a method on the server side is time-consuming, the client should not call it in the UI thread. Of course, do not call this long time-consuming method in the andonServiceConnected method.onServiceDisconnected

      • Calls to long-running methods should be placed on a non-UI thread. For example, another Thread to run.
    • Similarly, we can't call the client's time-consuming method in the main thread of the server. For example, the server's noticeStudentAddedwill call the client's callback interface method onStudentAdded. If it onStudentAddedtakes a lot of time, please ensure that the server noticeStudentAddedruns in a non-UI thread, otherwise the server will not be able to respond.

    • The client's onStudentAddedmethod runs in the client's Binder thread pool, so you cannot access UI-related content in the method. If you want to access the UI, use Handler to switch to the UI thread.
  3. Reconnection after Binder's unexpected death:

    1. Set death proxy for Binder: linkToDeathandunlinkToDeath

      private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
              @Override
              public void binderDied() {
                  if (remoteStudentManager == null) {
                      return;
                  }
                  remoteStudentManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
                  remoteStudentManager = null;
                  //重新绑定远程服务
                  Intent intent = new Intent(MainActivity.this, StudentManagerService.class);
                  bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
              }
          };

      Then after the client binds the remote service successfully, set the death proxy to the binder:

      service.linkToDeath(mDeathRecipient,0);
    2. onServiceDisconnectedReconnect the remote service in .

    3. Either of the two methods can be used at will. The difference between them is:

      • Binder's death proxy is called back in the client Binder thread pool, so it cannot access the UI.
      • onServiceDisconnectedCalled back on the client's UI thread.

4. Add permission verification function

There are two methods for authorization verification in AIDL:
1. Verify in the onBindmethod on the server side . If the verification fails, return null, and the client that fails the verification cannot bind the service. Here we use the permission authentication method.

  1. First declare the required permissions in the manifest:

    <permission android:name="com.dou.ipcsimple.permission.ACCESS_STUDENT_SERVICE" 
    android:protectionLevel="normal"/>
  2. onBindAdd permission validation for the method:

    public IBinder onBind(Intent intent) {
            Log.d("StudentManagerService", "Service onBind");
            int check = checkCallingOrSelfPermission("com.dou.ipcsimple.permission.ACCESS_STUDENT_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.e("StudentManagerService", "Service onBind :" + null);
                return null;
            }
            return binder;
        }

    At this point, the client cannot connect to the remote service. Looking at the Logcat, you can find the following information:

    com.dou.ipcsimple:remote E/StudentManagerService: Service onBind :null

  3. Register the permission in manifest:

    ```
    <uses-permission android:name="com.dou.ipcsimple.permission.ACCESS_STUDENT_SERVICE" />
    ```
    再次运行,客户端就可以连接上远程服务了。
    

2. onTransactPerform permission verification in the method of the server , and return false if the verification fails.

In addition to permission verification, you can also use Uid and Pid to verify, getCallingUidand getCallingPidyou can get the Uid and Pid of the application to which the client belongs.

    ```
    //StudentManagerService
    private Binder binder = new IStudentManager.Stub() {
            @Override
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
                int check = checkCallingOrSelfPermission("com.dou.ipcsimple.permission.ACCESS_STUDENT_SERVICE");
                if (check == PackageManager.PERMISSION_DENIED) {
                    return false;
                }
                String pkgName = null;
                String[] pkgs = getPackageManager().getPackagesForUid(getCallingUid());
                if (null != pkgs && pkgs.length > 0) {
                    pkgName = pkgs[0];
                }
                if (!pkgName.startsWith("com.dou")) {
                    return false;
                }
                return super.onTransact(code, data, reply, flags);
            }
            ...
            ...
            ...
        };
    ```

Obviously, this service can only successfully connect to the remote server if it declares usage rights and the client whose "com.dou.ipcsimple.permission.ACCESS_STUDENT_SERVICE"package name starts with .com.dou

4. Epilogue

The Binder communication mechanism will come to an end temporarily. The next article will introduce another IPC mechanism, Content Privider.

In this article, the complete code can be obtained from Github: IPCSimple .

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324737186&siteId=291194637