Sharing Functionality between Android Apps with AIDL

Share this article

While building Android apps you might sometimes need to expose functionality which other processes can use. As these calls are between separate processes, they cannot be a simple function, as one Android process cannot access the data in another.

To pass data between processes, it needs to be marshaled and unmarshaled accordingly. This marshaling and unmarshmaling of data to primitive types is so the OS can understand it passing over inter process communication (IPC) can be tedious and error prone if done manually. Android interface definition language (AIDL) helps solve this problem. AIDL generally has three components which are:

The interface library

Defines the interface using AIDL between the client and service. This library will be a dependency of both the client and the service implementing the interface.

The Service

This will be a separate application (apk) that implements the interface defined by the interface library.

The client

The client can be a separate application (apk) which will make calls to the service using the interface library.

In this article we are going to implement these components and look at how they are they used together. The complete code for this tutorial can be found on GitHub.

Defining an AIDL

To define an AIDL, we need a file with the .aidl extension. In this tutorial we will place the AIDL in a separate library project called androidaidllibrary. The Android SDK takes the AIDL and generates a ‘Binder’, implemented by the service.

The AIDL syntax is Java-like. You can use all primitive types in your AIDL definition and objects like ‘Strings’, ‘List’ and ‘Maps’. If you want to define your own objects, then you can, but you need to provide a class definition that implements the Parcelable interface.

In this example, we will define an interface called IRemoteProductService which will have two methods, addProduct and getProduct. To create the AIDL in Android Studio, select the File -> New -> AIDL -> AIDL File menu option to create a file called IRemoteProductService and add the following code to it. You will need to change the package and import names to match your application:

// IRemoteProductService.aidl
package com.androidaidl.androidaidllibrary;

import com.androidaidl.androidaidllibrary.Product;

interface IRemoteProductService {

    void addProduct(String name , int quantity, float cost);
    Product getProduct(String name);
}

If you try to build the project so far, it will fail due to using a custom product type, not yet defined. To define a custom type we need to do two things. First define the Product class which implements the Parcelable interface and an aidl file which defines the Product. Create the Product.java and Product.aidl files as below (Changing package names as relevant):

package com.androidaidl.androidaidllibrary;

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

public class Product implements Parcelable {

    private String name;
    private int quantity;
    private float cost;

    public Product(String name, int quantity, float cost) {
        this.name = name;
        this.quantity = quantity;
        this.cost = cost;
    }

    private Product(Parcel in) {
        name = in.readString();
        quantity = in.readInt();
        cost = in.readFloat();
    }

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

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeString(name);
        out.writeInt(quantity);
        out.writeFloat(cost);
    }

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

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


    public String getName() {
        return name;
    }

    public int getQuantity() {
        return quantity;
    }

    public float getCost() {
        return cost;
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", quantity=" + quantity +
                ", cost=" + cost +
                '}';
    }
}
// Product.aidl
package com.androidaidl.androidaidllibrary;

parcelable Product;

The Product.java is a simple Java file which defines the fields and functions for the Product and implements the methods necessary. writeToParcel writes the fields of the class to the parcel and creates a static object called CREATOR which can create the object from the parcel.

Now you should be able to build the project and see the auto generated IRemoteProductService.java in the build/generated folder.

Implementing the AIDL interface.

Once we have created the interface library, we need to implement the interface in the service. We will create a separate application to do this. By ‘separate application’, I mean a separate apk file. This can be a new project or a module in same project. By using a different apk, calls will be interprocesses which is what AIDL is designed for.

Add androidaidllibrary as a dependency to the application and create a service as shown below:

package com.androidaidl.androidaidlservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.androidaidl.androidaidllibrary.IRemoteProductService;
import com.androidaidl.androidaidllibrary.Product;

import java.util.ArrayList;
import java.util.List;
import java.util.Collections;


public class ProductService extends Service {

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

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteProductService.Stub mBinder = new IRemoteProductService.Stub() {

        List <Product> products = Collections.synchronizedList(new ArrayList<Product>());

        @Override
        public void addProduct(String name, int quantity, float cost) throws RemoteException {
            //Add product called on the service.
            //Idealy you should store the product in a local data base
            //or in some remote service.
            //You can add that code here . We are just storing in In memory list.
            Product product = new Product(name, quantity, cost);
            products.add(product);
        }

        @Override
        public Product getProduct(String name) throws RemoteException {
            //getProduct product called on the service.
            //Idealy you should store the product in a local data base
            //or in some remote service. Hence the product should be fetched from there.
            //You can add that code here .
            //We are just storing in In memory list.So fetching from in memory list.
            for(Product product : products) {
                if(product.getName().equalsIgnoreCase(name)) {
                    return product;
                }
            }
            return null;
        }
    };

}

When we built the IRemoteProductService.aidl file it generated a class called IRemoteProductService.Stub() which our binder needs to implement. Above we implemented that and stored products in an in memory map. The service returns this binder to the client in the onBind function. We need to declare the service in AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.androidaidl.androidaidlservice">

    <application android:allowBackup="true" android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme">

        <service android:name="com.androidaidl.androidaidlservice.ProductService">
            <intent-filter>
                <action android:name="com.androidaidl.androidaidlservice.ProductService" />
            </intent-filter>
        </service>
    </application>

</manifest>

Making the IPC call from the client.

Once we have built the interface library and the service which implements the interface we are ready to consume the service. To do this we build a client application that contains an Activity which will add and fetch products from this service through aidl and display them in the UI. Create an application (again, for the purposes of this tutorial, it can be in the same project) which depends on androidaidllibrary and has a layout and activity as follows

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.androidaidl.androidaidl.MainActivity"
    android:id="@+id/mainLayout">

    <LinearLayout android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Product name"
        android:id="@+id/txtName"
        />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/edtName"
        android:ems="10"
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Product Quantity"
        android:id="@+id/txtQuantity"
       />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:ems="10"
        android:id="@+id/edtQuantity"
        android:layout_below="@+id/editName"
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Product Cost"
        android:id="@+id/txtCost"
       />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="numberDecimal"
        android:ems="10"
        android:id="@+id/edtCost"
        />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Add Product"
        android:id="@+id/btnAdd"
        />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Search Product"
            android:id="@+id/txtSearch"
            />

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ems="10"
            android:id="@+id/edtSearchProduct"
            />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Search Product"
            android:id="@+id/btnSearch"

            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text=""
            android:id="@+id/txtSearchResult"
            />
    </LinearLayout>

</RelativeLayout>
package com.androidaidl.androidaidl;


import android.app.Activity;
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.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.androidaidl.androidaidllibrary.IRemoteProductService;
import com.androidaidl.androidaidllibrary.Product;


public class MainActivity extends Activity {

    private IRemoteProductService service;
    private RemoteServiceConnection serviceConnection;

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

        Button addProduct = (Button)findViewById(R.id.btnAdd);
        addProduct.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    if (service != null) {
                        String name = ((EditText) findViewById(R.id.edtName)).getText().toString();
                        int quatity = Integer.parseInt(((EditText) findViewById(R.id.edtQuantity)).getText().toString());
                        float cost = Float.parseFloat(((EditText) findViewById(R.id.edtCost)).getText().toString());

                        service.addProduct(name, quatity, cost);
                        Toast.makeText(MainActivity.this, "Product added.", Toast.LENGTH_LONG)
                                .show();
                    } else {
                        Toast.makeText(MainActivity.this, "Service is not connected", Toast.LENGTH_LONG)
                                .show();
                    }
                } catch (Exception e) {

                }
            }
        });

        Button searchProduct = (Button)findViewById(R.id.btnSearch);
        searchProduct.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    if (service != null) {
                        String name = ((EditText) findViewById(R.id.edtSearchProduct)).getText().toString();
                        Product product = service.getProduct(name);
                        if(product != null) {
                            ((TextView) findViewById(R.id.txtSearchResult)).setText(product.toString());
                        } else {
                            Toast.makeText(MainActivity.this, "No product found with this name", Toast.LENGTH_LONG)
                                    .show();
                        }

                    } else {
                        Toast.makeText(MainActivity.this, "Service is not connected", Toast.LENGTH_LONG)
                                .show();
                    }
                } catch(Exception e) {

                }
            }
        });


    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void connectService() {
        serviceConnection = new RemoteServiceConnection();
        Intent i = new Intent("com.androidaidl.androidaidlservice.ProductService");
        i.setPackage("com.androidaidl.androidaidlservice");
        boolean ret = bindService(i, serviceConnection, Context.BIND_AUTO_CREATE);
    }
    class RemoteServiceConnection implements ServiceConnection {

        public void onServiceConnected(ComponentName name, IBinder boundService) {
            service = IRemoteProductService.Stub.asInterface((IBinder) boundService);
            Toast.makeText(MainActivity.this, "Service connected", Toast.LENGTH_LONG)
                    .show();
        }

        public void onServiceDisconnected(ComponentName name) {
            service = null;
            Toast.makeText(MainActivity.this, "Service disconnected", Toast.LENGTH_LONG)
                    .show();
        }
    }
}

Above in the onCreate method of the activity we call connectService which creates the intent for the service and calls bindService, passing an implementation of ServiceConnection that is called when the service connects. When the service is connected the binder returns as the callback which is the implementation of the interface we store to use for calls.

In the UI, we create fields to enter the values to add a product and call addProduct on the service object stored. To fetch a product we call getProduct. Install both the apks (the service and the client activity) and you should be able to see the screen and add and fetch products as shown below.

Conclusion

Android makes it simple to communicate between processes. It provides tools and mechanisms to remove a lot of boilerplate code which would otherwise be needed.

AIDL provides a clean and a simple method to build components in Android for sharing functionality between apps and services.

Let me know if you have any questions or problems.

Frequently Asked Questions (FAQs) about AIDL for Sharing Functionality Between Android Apps

What is the main purpose of AIDL in Android development?

Android Interface Definition Language (AIDL) is primarily used for interprocess communication (IPC) in Android development. It allows different applications to interact with each other using remote procedure calls (RPCs). This means that an app can use functionalities of another app, running in a different process, as if they were in the same process. This is particularly useful when you want to share some functionalities between different apps or when you want to divide your app into different processes to isolate and protect data from each other.

How does AIDL differ from other IPC mechanisms in Android?

Android provides several mechanisms for IPC, including Intents, Bundles, and Messengers. However, AIDL is more powerful and flexible than these mechanisms. It allows for complex data types and supports multiple simultaneous clients, which is not possible with other mechanisms. Moreover, AIDL provides a strong type checking at compile time, which helps to catch errors early in the development process.

How can I define an AIDL interface?

To define an AIDL interface, you need to create an .aidl file in your project. This file should define an interface with methods that you want to expose to other apps. Each method should declare its parameters and return type. The parameters can be of any type that is parcelable or serializable, including custom types. Once you have defined the interface, the Android build system will generate a corresponding Java interface with the same methods.

How can I implement an AIDL interface in a service?

To implement an AIDL interface in a service, you need to create a subclass of the generated Java interface and implement its methods. Then, you need to create an instance of this subclass and return it from the onBind() method of your service. This will allow other apps to bind to your service and call its methods.

How can I call methods of an AIDL interface from a client app?

To call methods of an AIDL interface from a client app, you need to bind to the service that implements the interface. Once the service is bound, you can cast the returned IBinder to the AIDL interface and call its methods. Note that these method calls will be synchronous and may block the client’s main thread, so you should call them from a background thread.

How can I handle callbacks from a service to a client app using AIDL?

To handle callbacks from a service to a client app, you can define another AIDL interface for the callbacks. The client app should implement this interface and register its implementation with the service. The service can then call methods of this interface to send callbacks to the client app.

Can I use AIDL for communication between components of the same app?

Yes, you can use AIDL for communication between components of the same app. However, this is usually not necessary because components of the same app run in the same process by default and can directly access each other’s methods. You might want to use AIDL if you choose to run some components in different processes for isolation and protection of data.

What are the limitations of AIDL?

AIDL has some limitations. It does not support method overloading, meaning you cannot have two methods with the same name but different parameters in an AIDL interface. It also does not support default parameter values. Moreover, AIDL method calls are synchronous and can block the client’s main thread, which can lead to Application Not Responding (ANR) errors if not handled properly.

How can I handle exceptions in AIDL?

AIDL does not support throwing exceptions from a service to a client. If a method of an AIDL interface throws an exception, the method will return without executing any further code and the client will not be notified of the exception. To handle exceptions, you can define an error return value or an error callback in your AIDL interface.

Can I use AIDL with Kotlin?

Yes, you can use AIDL with Kotlin. The Android build system generates a Java interface for an AIDL interface, but you can implement this interface in a Kotlin class. You can also call methods of this interface from Kotlin code. However, note that AIDL does not support Kotlin-specific features, such as nullability annotations and default parameter values.

Abbas SuterwalaAbbas Suterwala
View Author

Abbas is a software engineer by profession and a passionate coder who lives every moment to the fullest. He loves open source projects and WordPress. When not chilling around with friends he's occupied with one of the following open source projects he's built: Choomantar, The Browser Counter WordPress plugin, and Google Buzz From Admin.

androidapichriswgoogleservices
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week