Android AIDL tutorial

Posted by allspiritseve on Fri, 07 Jan 2022 13:41:31 +0100

AIDL (Android Interface Definition Language) is an IDL language, which is used to generate code for interprocess communication (IPC) between two processes on Android devices. Through AIDL, you can obtain the data of another process in one process and call its exposed methods, so as to meet the needs of inter process communication. Generally, the application that exposes the method to other applications for calling is called the server, and the application that calls the method of other applications is called the client. The client interacts by binding the Service of the server.

AIDL is introduced in the official document:

Using AIDL is necessary only if you allow clients from different
applications to access your service for IPC and want to handle
multithreading in your service. If you do not need to perform
concurrent IPC across different applications, you should create your
interface by implementing a Binder or, if you want to perform IPC, but
do not need to handle multithreading, implement your interface using a
Messenger. Regardless, be sure that you understand Bound Services
before implementing an AIDL.

The first sentence is very important, "only when you allow different clients to access your service and need to deal with multithreading problems, you must use AIDL". In other cases, you can choose other methods, such as Messenger, or cross process communication. It can be seen that AIDL handles multi-threaded and multi client concurrent access, while Messenger handles single thread.
The following describes how to use AIDL.

1 create AIDL file

AIDL files can be divided into two categories. A class is used to declare data types that implement the Parcelable interface for other AIDL files to use those data types that are not supported by default. Another class is used to define interface methods and declare which interfaces to expose for client calls. The package name of the referenced data type needs to be clearly indicated in the AIDL file, even if the two files are under the same package name.

By default, AIDL supports the following data types:

  • Eight basic data types: byte, char, short, int, long, float, double and boolean
  • String,CharSequence
  • List type. The data carried by list must be of AIDL supported types or other declared AIDL objects
  • Map type. The data carried by the map must be of types supported by AIDL or other declared AIDL objects

Both the client and the server need to be created. We can create it in the server first, and then copy it to the client. Right click in Android Studio to create an AIDL file, as shown:


After creation, the system will create an AIDL folder by default. The directory structure under the folder is the package name of the project, and the AIDL file is in it. As shown in the figure:


There will be a default method in the file, which can be deleted or added.

2 implementation interface

After creating or modifying the aidl file, you need to build the project, and the Android SDK tool will generate it The aidl file is named java interface file (for example, the file name generated by IRemoteService.aidl is IRemoteService.java), which really plays a role in interprocess communication. The generated interface contains a subclass named stub (for example, IRemoteService.Stub), which is an abstract implementation of its parent interface and declares all methods in the aidl file.
To implement the AIDL generated interface, instantiate the generated Binder subclass (for example, IRemoteService.Stub) and implement the methods inherited from the AIDL file.
The following is an example of implementing the IRemoteService interface using an anonymous inner class:

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

Now, the Binder is an instance of the Stub class (a Binder), which defines the RPC interface of the server.

3. Service side public interface

After implementing the interface for the server, you need to expose the interface to the client for binding. Create a Service and implement onBind() to return the class instance of the generated Stub. The following is the sample code of the server:

public class RemoteService extends Service {
    private final String TAG = "RemoteService";

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        Log.d(TAG, "onBind");
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid() {
            return Process.myPid();
        }

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

We also need to register the Service we created in the manifest file, otherwise the client cannot bind the Service.

        <service
            android:name=".RemoteService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.aidl"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

4 client calls IPC method

When a client (such as an Activity) calls bindService() to connect to this service, the onServiceConnected() callback of the client will receive the binder instance returned by the onBind() method of the server.

The client must also have access to the interface class. Therefore, if the client and server are in different applications, the src / directory of the client application must contain A copy of the aidl file, which generates the android.os.Binder interface to provide the client with access to the aidl method. Therefore, we need to copy the aidl folder of the server to the java folder of the client at the same level without changing any code.

When a client receives an IBinder in the onServiceConnected() callback, it must call IRemoteService Stub. Asinterface (service) to convert the returned parameters to IRemoteService type. For example:

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

After obtaining the iRemoteService object, we can call the methods defined in AIDL. To disconnect, you can call the unbindService() method. The following is the sample code for the client:

public class MainActivity extends AppCompatActivity {
    private final String TAG = "ClientActivity";
    private IRemoteService iRemoteService;
    private Button mBindServiceButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBindServiceButton = findViewById(R.id.btn_bind_service);
        mBindServiceButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String text = mBindServiceButton.getText().toString();
                if ("Bind Service".equals(text)) {
                    Intent intent = new Intent();
                    intent.setAction("com.example.aidl");
                    intent.setPackage("com.example.aidl.server");
                    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
                } else {
                    unbindService(mConnection);
                    mBindServiceButton.setText("Bind Service");
                }
            }
        });
    }

    ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected");
            iRemoteService = null;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected");
            iRemoteService = IRemoteService.Stub.asInterface(service);
            try {
                int pid = iRemoteService.getPid();
                int currentPid = Process.myPid();
                Log.d(TAG, "currentPID: " + currentPid + ", remotePID: " + pid);
                iRemoteService.basicTypes(12, 123, true, 123.4f, 123.45,
                        "Hello, server. I'm the client");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mBindServiceButton.setText("Unbind Service");
        }
    };
}

5 transfer objects through IPC

In addition to the data types supported by default above, AIDL can also pass objects, but this class must implement the Parcelable interface. This class needs to be used between the two applications, so it also needs to be declared in the AIDL file. In order to avoid the error that the AIDL file cannot be created due to duplicate class names, you need to create the AIDL file first and then the class.
First, create an AIDL file on the server, such as rect AIDL, for example:

// Rect.aidl
package com.example.aidl.server;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

Then you can create the Rect class and make it implement the Parcelable interface. The example code is as follows:

public class Rect implements Parcelable {
    private int left;
    private int top;
    private int right;
    private int bottom;

    public Rect(int left, int top, int right, int bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

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

    @NonNull
    @Override
    public String toString() {
        return "Rect[left:" + left + ",top:" + top + ",right:" + right + ",bottom:" + bottom + "]";
    }
}

In this way, we can create iremoteservice A new method is added in Aidl to pass Rect objects. The example code is as follows:

// IRemoteService.aidl
package com.example.aidl.server;
import com.example.aidl.server.Rect;

// Declare any non-default types here with import statements

interface IRemoteService {
    /**
     * 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);

    int getPid();

    void addRectInOut(inout Rect rect);
}

Note that the guide package needs to be specified here:

import com.example.aidl.server.Rect;

Then add the new rect Aidl file and rect Java file and modified iremoteservice The Aidl file is synchronized to the same path of the client, as shown in the figure:


Under build, you can call the addRectInOut method on the client. The example code is as follows:

    ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iRemoteService = IRemoteService.Stub.asInterface(service);
            try {
                iRemoteService.addRectInOut(new Rect(1, 2, 3, 4));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

6 sample code download

aidl-sample

Topics: Android ipc aidl