Android About AIDL Communication, RemoteCallbackList Implements Server Callback Client

Keywords: Android Java Programming SDK

Android Interface Definition Language (AIDL)

The Android Interface Definition Language (A IDL) is similar to other interface languages (IDLs) you may have used.It allows you to define programming interfaces that are recognized by both clients and services so that they can communicate with each other using interprocess communication (IPC).In Android, one process usually cannot access the memory of another process.Therefore, in order to communicate, a process needs to break down its objects into primitives that the operating system understands and group them into objects that you can manipulate.Writing code to perform this marshalling operation is cumbersome, so Android will use AIDL to handle this for you.

AIDL file creation and use

  • Start by creating two Android projects, the Server process and the Client process

Create a Server process, named com.enjoy.myplugin; create a Client process, sign up for com.enjoy.plugin;

The Server process directory structure is as follows;

The directory structure of the Client process is as follows:

To make a simple note, IAidlCbListener.aidl is an interface for registering a fallback, IMyAidlInterface.aidl is an open interface provided by the Server process, that is, an interface that can be invoked by the Client, since AIDL can only pass Java basic types and several types, such as list and map, while Person.aidl is a custom type declared for passing objects.

It is important to emphasize that custom types and all AIDL files need to be synchronized in the Server and Client processes, that is, package names and content must be consistent.

  • Next, look at the AIDL file contents:
  1. IAidlCbListener.aidl
    // IAidlCbListener.aidl
    package com.enjoy.myplugin;
    
    // Declare any non-default types here with import statements
    
    interface IAidlCbListener {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void showServerMsg(String msg);
    }

     

  2. IMyAidlInterface.aidl
    // IMyAidlInterface.aidl
    package com.enjoy.myplugin;
    
    import com.enjoy.myplugin.IAidlCbListener;
    import com.enjoy.myplugin.Person;
    // Declare any non-default types here with import statements
    
    interface IMyAidlInterface {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);
    
        String getData();
        void sendData(String data);
        void addPerson(in Person person);
        void notifyClient(String notifyContent);
    
        void registerCb(IAidlCbListener icbi);
        void unRegisterCb(IAidlCbListener icbi);
    }
    

     

  3. Person.aidl
    // Person.aidl
    package com.enjoy.myplugin;
    
    // Declare any non-default types here with import statements
    
    parcelable Person;
    

     

Custom transfer data type Person.java

package com.enjoy.myplugin;

import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

public class Person implements Parcelable {

    public int age;
    public String addr;

    public Person(){}

    public Person(int age, String addr){
        this.age = age;
        this.addr = addr;
    }

    protected Person(Parcel in) {
        this.age = in.readInt();
        this.addr = in.readString();
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(age);
        dest.writeString(addr);
    }

    @NonNull
    @Override
    public String toString() {
        return "Person: age = "+age+",addr = "+addr;
    }
}

Custom types need to be serialized to be passed between processes.

Explain:

The AIDL and java files mentioned above are required for prior interaction between Server and Client, so they should exist simultaneously in the Server and Client processes, and ensure that the package name and content are consistent

Create a Service corresponding to AIDL in the Server process and implement the interface method provided externally in AIDL

AidlServerService.java

package com.enjoy.myplugin;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.enjoy.myplugin.Person;


public class AidlServerService extends Service {
    private static final String TAG = "Rayman AidlServerService";

    private RemoteCallbackList<IAidlCbListener> mCbs = new RemoteCallbackList<>();
    private com.enjoy.myplugin.Person newPerson = new com.enjoy.myplugin.Person();

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    class MyBinder extends IMyAidlInterface.Stub{

        public String mData;

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            Log.d(TAG, "basicTypes: anInt = "+anInt);
        }

        @Override
        public String getData() throws RemoteException {
            notifyClient("invoke notifyClient...");
            return mData;
        }

        @Override
        public void sendData(String data) throws RemoteException {
            mData = data;
        }

        @Override
        public void addPerson(Person person) throws RemoteException {
            newPerson = person;
        }

        @Override
        public void notifyClient(String notifyContent) throws RemoteException {
            mCbs.beginBroadcast();
            //Traverse through all registered Liisteners and invoke their implementation methods one by one, that is, notify all registrants
            for(int i=0;i<mCbs.getRegisteredCallbackCount();i++){
                IAidlCbListener cb = mCbs.getBroadcastItem(i);
                cb.showServerMsg(""+i+"msg from Server is:"+notifyContent+"-"+newPerson.toString());
            }
            mCbs.finishBroadcast();
        }

        @Override
        public void registerCb(IAidlCbListener icbi) throws RemoteException {
            Log.d(TAG, "registerCb: icbi = "+icbi+",mcbs = "+mCbs);
            mCbs.register(icbi);
        }

        @Override
        public void unRegisterCb(IAidlCbListener icbi) throws RemoteException {
            mCbs.unregister(icbi);
        }
    }
}

During the IPC process, the following situations may be considered:

When a client registers a callback method with a server in AIDL, the server considers whether the client exited unexpectedly (because the client applied Crash incorrectly or was dropped by Kill). The server does not know to call back the client yet, and an error occurred.

Client and server process status

In the process of interprocess communication, it is very likely that a process will die.If the living party at that time does not know that the other party is dead, the problem will arise.So how do we get the survival status of process B in process A?

android certainly provides us with a solution, that is, Binder's linkToDeath and unlinkToDeath methods. The linkToDeath method needs to pass in a DeathRecipient object. There is a binderDied method in the DeathRecipient class. When the process in which the binder object is located dies, the binderDied method will be executed, so we can do some exception handling, release resources and so on in the binderDied method.Made

The Android SDK provides an encapsulated object: RemoteCallbackList, which helps me automate Link-To-Death issues.

Here is a brief introduction to the RemoteCallbackList:

public class RemoteCallbackList
extends Object

java.lang.Object
   ↳ android.os.RemoteCallbackList<E extends android.os.IInterface>

The cumbersome task of maintaining a list of remote interfaces, often used to perform tasks from Service Callback to its client.Especially:

  • Track a set of registered IInterface Callback, note through its underlying uniqueness IBinder Identify by Call IInterface#asBinder.
  • Will be attached IBinder.DeathRecipient To each registered interface so that it can be cleared from the list when its process disappears.
  • Lock the underlying list of interfaces to handle multithreaded incoming calls and traverse a snapshot of the list in a thread-safe manner without holding its lock.

To use this class, simply create an instance with the service and call it when the client registers and unregisters the service register(E) and unregister(E) Method.Callback to the registered client, using beginBroadcast(), getBroadcastItem(int) and finishBroadcast().

If the registered callback process disappears, the class is responsible for automatically removing it from the list.If you want to do something else in this case, you can create one that implements the onCallbackDied(E) A subclass of the method.

The RemoteCallbackList helps us avoid the problem that two IPC processes crash unexpectedly during a call, causing callback failures or process crash.

The Client process implements binding to the Server process AIDL corresponding to the Service and calls the interface it provides

Implement the following code in the MainActivity of the Client process:

package com.enjoy.plugin;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.enjoy.myplugin.IAidlCbListener;
import com.enjoy.myplugin.IMyAidlInterface;
import com.enjoy.myplugin.Person;

public class MainActivity extends BaseActivity {

    private static final String TAG = "Rayman plugin_MainActivity";

    IMyAidlInterface myAidlInterface = null;
    private MyCbListener myCbListener = new MyCbListener();

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if(myAidlInterface != null){
                //Unexpected death on Server side
                try {
                    myAidlInterface.unRegisterCb(myCbListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                myAidlInterface.asBinder().unlinkToDeath(mDeathRecipient,0);
                myAidlInterface = null;
                //TODO:bindService again
                Intent intent = new Intent("android.intent.action.aidl");
                intent.setComponent(new ComponentName("com.enjoy.myplugin","com.enjoy.myplugin.AidlServerService"));
                bindService(intent,mConn, Context.BIND_AUTO_CREATE);
            }
        }
    };

    private ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            if(myAidlInterface != null){
                try {
                    //Set the death receiver, which is for the IBinder object
                    service.linkToDeath(mDeathRecipient,0);
                    //Call the interface method provided by the Server process through AIDL
                    myAidlInterface.sendData("testaidl...");
                    Log.d(TAG, "onServiceConnected: myCbListener = "+myCbListener);
                    if(myCbListener == null){
                        myCbListener = new MyCbListener();
                    }
                    //Call the interface method provided by the Server process through AIDL
                    myAidlInterface.addPerson(new Person(32,"Rayman"));
                    //Register Callback Listener
                    myAidlInterface.registerCb(myCbListener);
                    //Call getData method in log
                    Log.d(TAG, "onServiceConnected: data = "+myAidlInterface.getData());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("Rayman", "onCreate: this is plugin Activity...");

        Intent intent = new Intent("android.intent.action.aidl");
        intent.setComponent(new ComponentName("com.enjoy.myplugin","com.enjoy.myplugin.AidlServerService"));
        bindService(intent,mConn, Context.BIND_AUTO_CREATE);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            if(myAidlInterface != null && myAidlInterface.asBinder().isBinderAlive()){
                myAidlInterface.unRegisterCb(myCbListener);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        unbindService(mConn);
    }

    //Implement callback listener
    class MyCbListener extends IAidlCbListener.Stub{

        @Override
        public void showServerMsg(String msg) throws RemoteException {
            Log.d(TAG, "showServerMsg: "+msg);
        }
    }
}

Bind a Service in the MainActivity of the Client process and call the interface method provided by the Server process through AIDL.

In MainActivity, the Service is re-registered in the event of a Server process Died by using the DeathRecipient object and calling IBinder's linkToDeath and unLinkToDeath methods.

A brief introduction linkToDeath and unlinkToDeath:

/**
     * Interface for receiving a callback when the process hosting an IBinder
     * has gone away.
     * 
     * @see #linkToDeath
     */
    public interface DeathRecipient {
        public void binderDied();
    }

    /**
     * Register the recipient for a notification if this binder
     * goes away.  If this binder object unexpectedly goes away
     * (typically because its hosting process has been killed),
     * then the given {@link DeathRecipient}'s
     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
     * will be called.
     * 
     * <p>You will only receive death notifications for remote binders,
     * as local binders by definition can't die without you dying as well.
     * 
     * @throws RemoteException if the target IBinder's
     * process has already died.
     * 
     * @see #unlinkToDeath
     */
    public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
            throws RemoteException;

    /**
     * Remove a previously registered death notification.
     * The recipient will no longer be called if this object
     * dies.
     * 
     * @return {@code true} if the <var>recipient</var> is successfully
     * unlinked, assuring you that its
     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
     * will not be called;  {@code false} if the target IBinder has already
     * died, meaning the method has been (or soon will be) called.
     * 
     * @throws java.util.NoSuchElementException if the given
     * <var>recipient</var> has not been registered with the IBinder, and
     * the IBinder is still alive.  Note that if the <var>recipient</var>
     * was never registered, but the IBinder has already died, then this
     * exception will <em>not</em> be thrown, and you will receive a false
     * return value instead.
     */
    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);

It is relatively simple to use, only the bindDied method of DeathRecipient needs to be implemented.

linkToDeath and unLinkToDeath appear in pairs, referring to the implementation of MainActivity in Client above, when linking onServiceConnected in ServiceConnection, unlink in the bindDied method.linkToDeath is the death agent set for the IBinder object, unLinkToDeath is the death agent set before it is unbound, and rebound can be done at this time.

Run the code to see the results

Compile the Server process and Client inheritance, and install them on the same phone or virtual machine. Start the Server process first, then the Client process. You can see the following results:

Note that this log content is for the Client process only, and there are no more screenshots of the Server process's log.From the log, you can see that the AIDL interface methods provided by the Server process invoked in the client process have been successfully invoked, and the listener that calls back the Client process settings on the server side has also executed successfully, indicating the current code OK.

Summary:

  1. Attention to understanding the Binder mechanism
  2. AIDL only provides limited transport data types, custom transport types need to be serialized
  3. RemoteCallbackList is a good solution to the problem of one party's Crash causing the other party's Exception during the IPC process
  4. DeathRecipient understanding and invocation (resolves the problem of unexpected termination of Server-side processes and loss of Binder objects acquired by Client-side)

OK, end

Seven original articles were published. Approved 0. Visits 109
Private letter follow

Posted by BPWheeler on Tue, 04 Feb 2020 19:33:10 -0800