Sharing Functionality between Android Apps with AIDL

Abbas Suterwala
Share

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.

CSS Master, 3rd Edition