AIDL interprocess communication

AIDL (Android Interface Definition Language) is similar to other IDLs.

It allows us to define the programming interface that both the client and service agree upon in order to communicate with each other using interprocess communication (IPC).

On Android, one process cannot normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and marshall the objects across that boundary for you. The code to do that marshaling is tedious to write, so Android handles it for you with AIDL.

# When to use AIDL

  1. Clients from different applications want to access your service for IPC.

# The data types supported by AIDL

  • All primitive types (Supported by Java such as int, long, char, boolean, and so on)

# AIDL Implementation Steps

In order to allow for one application to call into another, we typically have to:

  1. Define the AIDL interface

To create a service we need to define an interface between the client and the server. AIDL is the way we do that, simply add a file with .aidl extension and the AIDL tool supplied with Android SDK will generate a code for using in the server (stub) and the client(proxy).

The best way to build client and server applications is to create the common code in a library

  • Create an Android Studio project — select “phone or tablet” template no activity — this will be the server application for hosting our services

package app.mabel.com.mylib;

interface ISimp {

int add(int a,int b);

int sub(int a,int b);

}

  • Build the module — from the build menu select make module mylib

The generated code

If you change the view project files and go to mylib/build/generated/source , you will found a generated java file ISimp.java with the generated code from your AIDL

The generated code looks like this: (I removed the inner methods to simplify the explanation)

package app.mabel.com.mylib;

public interface ISimp extends android.os.IInterface {

public static abstract class Stub extends android.os.Binder implements app.mabel.com.mylib.ISimp {

private static class Proxy implements app.mabel.com.mylib.ISimp {

}

}

public int add(int a, int b) throws android.os.RemoteException;

public int sub(int a, int b) throws android.os.RemoteException;

}

The interface in the AIDL file converted into a java interface. The Stub class is used by the service implementation In the server app. The proxy class is used by the client application.

Writing the server application

The first thing we need to do is adding dependency between the server application and the android library module:

  • Right-click on the server application -> Open Module Settings -> Dependencies -> click on the green plus icon on the upper right corner -> Select Module dependency -> select mylib

Add a new java class to the project — SimpServiceImp.java. The class should derive from ISimp.Stub class. The generated Stub class is abstract so we need to implement the missing methods — the members from the interface ISimp:

package app.mabel.com.testservice;

import android.os.RemoteException;

import app.mabel.com.mylib.ISimp;

public class SimpServiceImp extends ISimp.Stub {

@Override

public int add(int a, int b) throws RemoteException {

return a+b;

}

@Override

public int sub(int a, int b) throws RemoteException {

return a-b;

}

}

Now to create an Android Service we need to add another class derived from Service class and implement onBind and return the service implementation (Java does not support multiple inheritances so there is no way to derive from both the Stub and Service)

Create a new class: MySimpService.java

package app.mabel.com.testservice;

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

import android.support.annotation.Nullable;

public class MySimpService extends Service {

@Nullable

@Override

public IBinder onBind(Intent intent) {

return new SimpServiceImp();

}

}

The last thing to do is declare the service in AndroidManifest.xml file:

<manifest xmlns:android=”http://schemas.android.com/apk/res/android"

package=”app.mabel.com.testservice”>

<application

android:allowBackup=”true”

android:icon=”@mipmap/ic_launcher”

android:label=”@string/app_name”

android:roundIcon=”@mipmap/ic_launcher_round”

android:supportsRtl=”true”

android:theme=”@style/AppTheme” >

<service android:name=”.MySimpService”>

<intent-filter>

<action android:name=”app.mabel.com.testservice.MySimpService”/>

</intent-filter>

</service>

</application>

</manifest>

To install the server app select Run -> Edit Configuration and select Nothing in the Launch list (this will only install the APK without trying to run it). then run the application

Writing the Client application

Add a new module to the project — select Phone & Tablet module , select a simple activity. This will create a new APK module so now our project built from 2 APKs and one AAR

Add a dependency to mylib module

To bind to the service you first need to implement ServiceConnection Interface. You can do it using a separate class or anonymous class but in this simple example we use the MainActivity class:

public class MainActivity extends AppCompatActivity implements ServiceConnection{

@Override

public void onServiceConnected(ComponentName componentName, IBinder iBinder) {

}

@Override

public void onServiceDisconnected(ComponentName componentName) {

}

}

Before we implement the ServiceConnection interface we need to bind the service — we will do it in onStart event:

protected void onStart() {

super.onStart();

bindService(new Intent(“app.mabel.com.testservice.MySimpService”).setPackage(“app.mabel.com.testservice”)

,this,BIND_AUTO_CREATE);

}

We use the service action name as declared in the AndroidManifest.xml file and we need to specify the server package to create an explicit intent

Now add a ISimp member to the class and implement the onServiceConnected method:

private ISimp service;

@Override

public void onServiceConnected(ComponentName componentName, IBinder iBinder) {

service = ISimp.Stub.asInterface(iBinder);

}

The method asInterface returns a Proxy object to use by the client. Now we can add a button with event and call the service using the proxy:

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Button btn=(Button)findViewById(R.id.btn1);

btn.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

try {

Log.d(“tag”,”res:”+service.add(10,20));

} catch (RemoteException e) {

e.printStackTrace();

}

}

});

}

How does it work

We can see the flow from the generated code, when we call add, the add method from the Proxy class is invoked, It serialises the parameters and sends it to the binder:

public int add(int a, int b) throws android.os.RemoteException {

android.os.Parcel _data = android.os.Parcel.obtain();

android.os.Parcel _reply = android.os.Parcel.obtain();

int _result;

try {

_data.writeInterfaceToken(DESCRIPTOR);

_data.writeInt(a);

_data.writeInt(b);

mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);

_reply.readException();

_result = _reply.readInt();

} finally {

_reply.recycle();

_data.recycle();

}

return _result;

}

using mRemote.transact the proxy sends the request to the binder, then to the service Stub member onTransact:

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_add: {

data.enforceInterface(DESCRIPTOR);

int _arg0;

_arg0 = data.readInt();

int _arg1;

_arg1 = data.readInt();

int _result = this.add(_arg0, _arg1);

reply.writeNoException();

reply.writeInt(_result);

return true;

}

}

The function deserialize the parameters, call the service implementation to add method (what we implemented above) and serialize the return value

Extending the interface

Now we can add more methods to the interface, implement on the service and use from the client

in, out, inout keywords

When using a primitive type, they passed and return by value but if we send an object to a java method, it is passed by reference. To make it work on another process we need to serialize the parameters from the client to the service and serialize it back because maybe the function changed it. To make it more effective we need to specify if the object should be input (from the client to the service only), output (from the service to the client only) or input and output. We do that with one of the above keywords

For example, we want to add a method to calculate array items sum. we need to pass the array to the service but we don’t care about changes done by the service so we add it to the AIDL file within keyword:

interface ISimp {

int add(int a,int b);

int sub(int a,int b);

int addrr(in int[] arr);

}

implement the service implementation class and use it from the client

@Override

public int addrr(int[] arr) throws RemoteException {

int sum=0;

for(int i=0;i<arr.length;i++)

sum+=arr[i];

return sum;

}

If we want a function to fill the array with values we need to specify it as out and if we read and write values on the service (get an image, convert it to greyscale) we use inout:

interface ISimp {

int add(int a,int b);

int sub(int a,int b);

int addrr(in int[] arr);

int fillArray(out int[] arr)

int convertToGray(inout int[] rgb)

}

Creating custom types

You can pass a very limited number of types using the binder :

  • All primitives

You can add a new type by implementing Parcelable interface: (do it in the library)

package app.mabel.com.mylib;

import android.os.Parcel;

import android.os.Parcelable;

public class CustomType implements Parcelable {

int num1;

String s1;

public int getNum1() {

return num1;

}

public void setNum1(int num1) {

this.num1 = num1;

}

public String getS1() {

return s1;

}

public void setS1(String s1) {

this.s1 = s1;

}

@Override

public int describeContents() {

return 0;

}

@Override

public void writeToParcel(Parcel parcel, int i) {

parcel.writeInt(num1);

parcel.writeString(s1);

}

public static final Parcelable.Creator<CustomType> CREATOR = new Parcelable.Creator<CustomType>(){

@Override

public CustomType createFromParcel(Parcel parcel) {

CustomType res=new CustomType();

res.num1 = parcel.readInt();

res.s1 = parcel.readString();

return res;

}

@Override

public CustomType[] newArray(int size) {

return new CustomType[size];

}

};

}

Serialize the object in writeToParcel method and deserialize it on createFromParcel. Note that the order is important (in this example first is integer and second is srting)

We also need to add another aidl file CustomType.aidl with the parcelable definition:

package app.mabel.com.mylib;

parcelable CustomType;

Now we can add the custom type as a parameter in our interface:

package app.mabel.com.mylib;

import app.mabel.com.mylib.CustomType;

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

interface ISimp {

int add(int a,int b);

int sub(int a,int b);

int addrr(in int[] arr);

String getCustomName(in CustomType ct);

}

Note that we need to import the custom type even if it's on the same package

Asynchronous Service

Another useful option is declaring an async method in the service. We do it using oneway keyword and we need to split the operation into 2 interfaces — one for the request and one for the response.

For example we want to sum the array elements asynchronously. We need to create 2 interfaces:

one for the request: IAsync.aidl

package app.mabel.com.mylib;

import app.mabel.com.mylib.IAsyncListener;

oneway interface IAsync {

void calcSum(in int []arr, in IAsyncListener lis);

}

And one for the response: IAsyncListener.aidl

package app.mabel.com.mylib;

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

oneway interface IAsyncListener {

void OnResponse(int res);

}

Now we implement the service:

public class IAsyncImp extends IAsync.Stub {

@Override

public void calcSum(int[] arr, IAsyncListener lis) throws RemoteException {

int res=0;

for(int i=0;i<arr.length;i++)

res+=arr[i];

lis.OnResponse(res);

}

}

And use it from the client

Button btn2=(Button)findViewById(R.id.btn2);

btn2.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

try {

asyncService.calcSum(new int[]{2, 3, 4, 5}, new IAsyncListener.Stub() {

@Override

public void OnResponse(int res) throws RemoteException {

Log.d(“tag”,”resp:”+res);

}

});

} catch (RemoteException e) {

e.printStackTrace();

}

}

});

Note that the client derived from the response interface Stub (i.e. the client act as a server for the result)

That's it.

This is the basis for any type of service in android, next post will be on native services integrated into AOSP.

In this article, I have covered about AIDL. For more information, I will write some more articles. So to know about that please keep following and give a clap if you like this article.

I hope you enjoyed this story. If you have any comments or questions, please join the forum discussion below!

Android Developers ProAndroidDev.com, Hackernoon, freeCodeCamp, Google Developers, AndroidPIT.com, Xyz Zyx, Android

Thanks for the support!

A developer is responsible for developing a framework and system applications for devices powered by the Android operating system.