Senior Software Engineer & Architect. I have programmed with a variety of languages. Originally:C/C++, Mostly: Java, Occasionally: PHP, VB, Python, Unix Shell, JavaScript, Mainframe EasyTrieve and JCL. I also do Android development at the present time. Tony is a DZone MVB and is not an employee of DZone and has posted 36 posts at DZone. You can read more from them at their website. View Full User Profile

Android AIDL and Remote Client

03.07.2012
| 12381 views |
  • submit to reddit
Here's a concrete example of a client using Android Interface Definition language (AIDL) for Inter-Process communication (IPC) with a Service setup in another Android application on the phone.

In the previous article, we described how to set up a remote service using AIDL. Before that, in a first article, we showed an implementation of a custom Parcelable User class to pass across processes. Now, we're going to look at the client and finally produce a little concrete example on the phone. Talking about concepts is all fine and dandy, but as engineers, we need to have some sort of working software in the end. An AIDL Client/Service .zip file will be included at the end of this article for download, so the reader can play around with the code.

Since we need to test access to the service from a different process, we start by creating a separate Android project from our service. We'll need a very basic UI so that we can test our remote service. Therefore, our client must be an Activity. Our client must also be able to:

  1. Connect to the service, i.e. bind to it using a ServiceConnection
  2. Get the data it needs using the generated service stub


Our basic client will have the following skeleton:

package com.ts.dataclient;

import android.app.Activity;
import android.content.ServiceConnection;
// etc...
// import the Service aidl files
import com.ts.userdata.User;
import com.ts.userdata.IUserDataService;

/**
 * Client screen to bind to UserDataService
 * */
public class DataClientActivity extends Activity {

	/** Service to which this client will bind */
	IUserDataService dataService;
	/** Connection to the service (inner class) */
	DataConnection  conn;

       // etc...

}


And we'll implement our ServiceConnection as an inner class of the client Activity:

 /** Inner class used to connect to UserDataService */
    class DataConnection implements ServiceConnection { 

    	/** is called once the bind succeeds */
        public void onServiceConnected(ComponentName name, IBinder service) {
        	dataService = IUserDataService.Stub.asInterface(service);
        }

        /*** is called once the remote service is no longer available */
        public void onServiceDisconnected(ComponentName name) { //
        	dataService = null;
        }

      }


The Stub.asInterface method was one of the automatically generated methods by the AIDL tool when we set up our service. The rest is pretty much self-documenting.

Next, we'll implement a couple of Activity lifecycle methods for the client:

/** Called when the activity is first created */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // connect to the service
        conn = new DataConnection();
        // name must match the service's Intent filter in the Service Manifest file
        Intent intent = new Intent("com.ts.userdata.IUserDataService");
        // bind to the Service, create it if it's not already there
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /** Clean up before Activity is destroyed */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
        dataService = null;
    }

Next, we'll create a very basic UI consisting of a field and a submit button. Here are the most relevant parts:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...>
   ...
   <EditText android:id="@+id/uid" ../>
   <Button android:text="Submit" android:onClick="getData" ../>
</linearLayout>


Then we need to get the actual data we want from the remote service. On submit (onClick handler), we display a Toast message with the data :

 /** Handler for Submit button */
    public void getData(View v){

    	User usr = null; // User is Parcelable @see first article
    	try {
    		// get user input
    		EditText userid = (EditText) findViewById(R.id.uid);
    	        long id = Long.parseLong( userid.getText().toString().trim() );
    		// call the AIDL service
    		 usr = dataService.lookUpUser(id); 

    	 }
    	 catch(NumberFormatException nfex){
    	    	Toast.makeText(this, "Id must be a number.", Toast.LENGTH_SHORT).show();
    	 }
    	 catch (RemoteException e) {
    	        Toast.makeText(this, "Service unavailable", Toast.LENGTH_SHORT).show();
    	 }

    	 if(usr != null){
    	    	Toast.makeText(this, usr.toString(), Toast.LENGTH_LONG).show();
	 }
    }


We're almost done. Back to our service in the previous article, we'll add a few lines of code to return something concrete to clients, so we can test that it all works:

package com.ts.userdata;

import com.ts.userdata.IUserDataService;

// other imports here

/** Laughably simple service implementation. We'll return some data if user enters id=101.
  * For all other values, we return a non-registered, empty set */
public class UserDataService extends Service {

	@Override
	public IBinder onBind(Intent arg0) {
		return new IUserDataService.Stub() { // generated by the System
	          public User lookUpUser(long id) throws RemoteException {
	        	 // TEST
	        	  User usr = new User();
	        	  usr.setId(101L);
	        	  usr.setAge(30);
	        	  usr.setPhone("123 456 7890");
	        	  usr.setRegistered(true);	  

	        	  // Anonymous user
	        	  User anonym = new User();
	        	  anonym.setPhone("Not Published");
	        	  anonym.setRegistered(false);

	        	  return ( id == usr.getId() ? usr : anonym );
	          }
	    };
	}
}


The above implementation is of course hidden to the clients, and is not distributed to anyone outside the service. However, the client needs access to the AIDL files and also the Parcelable User class (more on that later) to be able to use the service. So we need to copy those with their original package structure to the client project. For our demonstration purposes, we'll deploy both applications on the phone at the same time just by deploying the client. The easiest way to do that is to include the service application in the client application's build path (in Eclipse, Go to Project/Properties/Java Build Path/ and add the Service project). Once we do that and deploy, here's what we get on the actual phone:


The IPC worked. We have effectively communicated across processes, i.e. Android applications.

Note that we have to distribute not only the AIDL files, but also our custom Parcelable User class to our clients since they need to get it at the other end of IPC channel. That could be a problem because User is not an interface. If we ever need to change its implementation, we will break our existing clients. A simpler and more stable AIDL service would be one that only uses types provided by the system that are supported out-of-the-box, like our phone lookup service in the previous article, which returned a List of String types. So the final word is, keep those issues in mind when deciding to create your own Parcelable classes in AIDL.

AIDL Client/Service .zip file: Android-AIDL.zip


Published at DZone with permission of Tony Siciliani, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)