Monday, December 3, 2012

AIDL interface in Android


Why and What's AIDL ?

Each application in Android runs in its own process. An application cannot directly access the data of another application for security reasons. However, a couple of mechanisms allow communication between applications. One such mechanism that you’ve seen throughout the book is Intents. Intents are asynchronous, meaning that you can post a message for someone to receive at some future point in time and just continue with your application.

Every once in a while we need a more direct, synchronous access to another process. There are many ways to implement this across process boundaries, and collectively they are called Interprocess Communication, or IPC for short.

To allow cross-application communication, Android provides its own version of an IPC protocol. One of the biggest challenges in IPC is passing data around, such as when passing parameters to method calls on the remote systems. IPC protocols tend to get complicated because they have to convert data from its in-memory format to a format that’s convenient for sending to another process. This is called marshaling, and the unpacking at the receiver is called unmarshaling.

To help with this, 
 "Android provides the Android Interface Definition Language, or AIDL. It is a lightweight implementation of IPC using a syntax that is very familiar to Java developers, and a tool that automatically creates the hidden code required to connect a client and a remote service."

To illustrate how to use AIDL to create interprocess communication, we’ll create two applications: a remote service called LogService and a client called LogClient that will bind to that remote service.


Implementing the Remote Service

Our remote service, LogService, will simply allow remote clients to log a message to it.

We are going to start by creating the interface for the remote service. This interface represents the API, or set of capabilities that the service provides. We write this interface in AIDL language and save it in the same directory as our Java code with an .aidl extension.

The AIDL syntax is very similar to a regular Java interface. You simply define the method signature. The datatypes supported by AIDL are somewhat different from regular Java interfaces. But all Java primitive datatypes are supported. So are the String, List, Map, and CharSequence classes.

If you have a custom complex data type, such as a class, you need to make it Parcelable so that the Android run-time can marshal and unmarshal it. In this example, we’ll create a Message as a custom type.

Writing the AIDL

We start by defining the interface for our service. As you can see, the interface very much resembles a typical Java interface. For readers who might have worked with CORBA in the past, AIDL has its roots in CORBA’s IDL.

Example 14.1. ILogService.aidl
package com.marakana.logservice; // 1

import com.marakana.logservice.Message; // 2

interface ILogService { //3
  void log_d(String tag, String message); // 4
  void log(in Message msg); // 5
}

1 Just as in Java, our AIDL code specifies what package it’s part of.
2 However, unlike Java, we have to explicitly import other AIDL definitions even if they are in the same package.
3 We specify the name of our interface. Interface names conventionally start with I for interface.
4 This method is simple because it doesn’t return anything and takes only primitives as inputs. Note that the String class is not a Java primitive, but AIDL considers it to be one.
5 This method takes our custom Message parcel as its input. We’ll define Message next.
Next, we’ll look at the implementation of the Message AIDL.

Example 14.2. Message.aidl
package com.marakana.logservice; // 1

/* 2 */
parcelable Message;

1 Specifies the package it’s in.
2 Declares that Message is a parcelable object. We will define this object later in Java.
At this point, we are done with the AIDL. As you save your files, Eclipse automatically builds the code to which the client will connect, called the stub because it looks like a complete method to the client but actually just passes on the client request to your remote service. The new Java file is located in Gen folder under /gen/com/marakana/logservice/LogService.java. Because this file is derived from your AIDL, you should never modify it. The aidl tool that comes with the Android SDK will regenerate it whenever you make any changes to your AIDL files.

Now that we have the AIDL and the generated Java stub, we are ready to implement the service.

Implementing the Service

Just like any Android service, we implement LogService in a Java class that subclasses the system Service class. But unlike our earlier Service implementations, where we ignored onBind() but implemented onCreate(), onStartCommand(), and onDestroy(), here we’re going to do the opposite. A method in a remote service starts when the client makes its request, which is called binding to the service, and therefore the client request triggers the service’s onBind() method.

To implement our remote service, we’ll return an IBinder object from the onBind() method in our service class. IBinder represents the implementation of the remote service. To implement IBinder, we subclass the ILogService.Stub class from the auto-generated Java code, and provide the implementation for our AIDL-defined methods, in this case various log() methods.

Example 14.3. LogService.java
package com.marakana.logservice;

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

public class LogService extends Service { // 1

  @Override
  public IBinder onBind(Intent intent) { // 2
    final String version = intent.getExtras().getString("version");

    return new ILogService.Stub() { // 3

      public void log_d(String tag, String message) throws RemoteException { // 4
        Log.d(tag, message + " version: " + version);
      }

      public void log(Message msg) throws RemoteException { // 5
        Log.d(msg.getTag(), msg.getText());
      }
    };
  }

}

1 LogService is an Android class derived from Service. We’ve seen many services, but this time around, it’s a bound service as opposed to UpdaterService, which was unbound.
2 Since this is a bound service, we must implement onBind() and have it return a correct instance of IBinder class. The client passes us an Intent, from which we extract some string data. During the client implementation, we’ll see how it sets this, and thus how we can pass small amounts of data into the remote service as part of the binding process.
3 This instance of IBinder is represented by ILogService.Stub(), a helper method that is generated for us in the Java stub file created by the aidl tool when we saved our AIDL interface. This code is part of /gen/com/marakana/logservice/LogService.java.
4 log_d() is the simple method that takes two strings and logs them. Our implementation simply invokes the system’s Log.d().
5 We also provide a log() method that gets our Message parcel as its input parameter. Out of this object we extract the tag and the message. Again, for this trivial implementation, we just invoke Android’s logging mechanism.
Now that we have implemented the service in Java, we have to provide the Java implementation of the Message parcel as well.

Implementing a Parcel

Since Message is a Java object that we’re passing across processes, we need a way to encode and decode this object—marshal and unmarshal it—so that it can be passed. In Android, the object that is capable of doing that is called a Parcel and implements the Parcelable interface.

To be a parcel, this object must know how to write itself to a stream and how to recreate itself.

Example 14.4. Message.java
package com.marakana.logservice;

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

public class Message implements Parcelable { // 1
  private String tag;
  private String text;

  public Message(Parcel in) { // 2
    tag = in.readString();
    text = in.readString();
  }

  public void writeToParcel(Parcel out, int flags) { // 3
    out.writeString(tag);
    out.writeString(text);
  }

  public int describeContents() { // 4
    return 0;
  }

  public static final Parcelable.Creator<Message> CREATOR = new Parcelable.Creator<Message>() { // 5

    public Message createFromParcel(Parcel source) {
      return new Message(source);
    }

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

  };

  // Setters and Getters 6
  public String getTag() {
    return tag;
  }

  public void setTag(String tag) {
    this.tag = tag;
  }

  public String getText() {
    return text;
  }

  public void setText(String text) {
    this.text = text;
  }

}

1 As we said before, Message implements the Parcelable interface.
2 To be parcelable, this object must provide a constructor that takes in a Parcel and recreates the object. Here we read the data from the parcel into our local variables. The order in which we read in data is important: it must correspond to the order in which the data was written out as well.
3 writeToParcel() is the counterpart to the constructor. This method is responsible for taking the current state of this object and writing it out into a parcel. Again, the order in which variables are written out must match the order in which they are read in by the constructor that gets this parcel as its input.
4 We’re not using this method because we have no special objects within out parcel.
5 A parcelable object must provide a Creator. This Creator is responsible for creating the object from a parcel. It simply calls our other methods.
6 These are just various setter and getter methods for our private data.
At this point, we have implemented the required Java code. We now need to register our service with the Manifest file.

Registering with the Manifest File

As always, whenever we provide one of the new main building blocks for applications, we must register it with the system. The most common way to do that is to define it in the Manifest file.

Just as we registered UpdaterService before, we provide a <service> element specifying our service. The difference this time around is that this service is going to be invoked remotely, so we should specify what Action this service responds to. To do that, we specify the action and the Intent Filter as part of this service registration.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.marakana.logservice" android:versionCode="1"
  android:versionName="1.0">
  <application android:icon="@drawable/icon" android:label="@string/app_name">

  <!-- 1 -->
    <service android:name=".LogService">
      <!-- 2 -->
      <intent-filter>
        <action android:name="com.marakana.logservice.ILogService" />
      </intent-filter>
    </service>

  </application>
  <uses-sdk android:minSdkVersion="4" />
</manifest>
1 This is where we define our service. It is a <service> element within the application block.
2 The difference between this service and our UpdaterService is that this service is going to be remote to the client, so calling it by an explicit class name wouldn’t work well since the client may not have access to the same set of classes. So instead we provide the Intent filter and action to which this service is registered to respond.
At this point, our service is complete. We can now move on to the client implementation.

Implementing the Remote Client

Now that we have the remote service, we are going to create a client that connects to that service to test that it all works well. Note that in this example we purposely separated the client and the server into two separate projects with different Java packages altogether, in order to demonstrate how they are separate apps.

So we’re going to create a new Android project in Eclipse for this client, just as we’ve done before for various other applications. However, this time around we are also going to make this project depend on the LogService project. This is important because LogClient has to find the AIDL files we created as part of LogService in order to know what that remote interface looks like. To do this in Eclipse:

  1. After you have created your LogClient project, right-click on your project in Package Explorer and choose Properties.
  2. In the "Properties for LogClient" dialog box, choose Java Build Path, then click on the Projects tab.
  3. In this tab, click on "Add…" and point to your LogService project.
This procedure will add LogService as a dependent project for LogClient.

Binding to the Remote Service

Our client is going to be an activity so that we can graphically see it working. In this activity, we’re going to bind to the remote service and from that point on use it as if it was just like any other local class. Behind the scenes, the Android binder will marshal and unmarshal the calls to the service.

The binding process is asynchronous, meaning we request it and it happens at some later point in time. To handle that, we need a callback mechanism to handle remote service connections and disconnections.

Once we have the service connected, we can make calls to it as if it was any other local object. However, if we want to pass any complex data types, such as a custom Java object, we have to create a parcel for it first. In our case, we have Message as a custom type, and we have already made it parcelable.

Example 14.5. LogActivity.java
package com.marakana.logclient;

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.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

import com.marakana.logservice.ILogService;
import com.marakana.logservice.Message;

public class LogActivity extends Activity implements OnClickListener {
  private static final String TAG = "LogActivity";
  ILogService logService;
  LogConnection conn;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Request bind to the service
    conn = new LogConnection(); // 1
    Intent intent = new Intent("com.marakana.logservice.ILogService"); // 2
    intent.putExtra("version", "1.0"); // 3
    bindService(intent, conn, Context.BIND_AUTO_CREATE); // 4

    // Attach listener to button
    ((Button) findViewById(R.id.buttonClick)).setOnClickListener(this);
  }

  class LogConnection implements ServiceConnection { // 5

    public void onServiceConnected(ComponentName name, IBinder service) { // 6
      logService = ILogService.Stub.asInterface(service); // 7
      Log.i(TAG, "connected");
    }

    public void onServiceDisconnected(ComponentName name) { // 8
      logService = null;
      Log.i(TAG, "disconnected");
    }

  }

  public void onClick(View button) {
    try {
      logService.log_d("LogClient", "Hello from onClick()"); // 9
      Message msg = new Message(Parcel.obtain()); // 10
      msg.setTag("LogClient");
      msg.setText("Hello from inClick() version 1.1");
      logService.log(msg); // 11
    } catch (RemoteException e) { // 12
      Log.e(TAG, "onClick failed", e);
    }

  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroyed");

    unbindService(conn); // 13

    logService = null;
  }
}

1 LogConnection is our class that connects to, and handles disconnections from, the remote service. The class is explained later.
2 This is the action Intent that we’re using to connect to the remote service. It must match the action that LogService specified in the Manifest file as part of its Intent filter.
3 Here is where we add the data to the Intent, to be extracted by the remote method.
4 bindService() is the method you use to ask the Android runtime to bind this activity to the remote service specified by the Intent action. In addition to the Intent, we pass on the Service Connection class to handle the actual connection. The BIND_AUTO_CREATE flag indicates that if the service we’re trying to connect to doesn’t already exist, it should be created.
5 LogConnection is the class that will be called back upon successful connection to the remote service, and whenever the service disconnects. This class needs to subclass ServiceConnection and implement onServiceConnected() and onServiceDisconnected().
6 onServiceConnected() is called once the bind succeeded. At this point, the IBinder instance represents our remote service.
7 We now need to cast the bound service into our LogService instance. To do that, we use a helper method named ILogService.Stub.asInterface(), provided by that Java stub that was created automatically by the aidl tool when we saved our AIDL files.
8 onServiceDisconnected() is called once the remote service is no longer available. It is an opportunity to handle any necessary cleaning up. In this case, we just set logService to null to help with the garbage collection.
9 Assuming that we have successfully bound to the remote service, we can now make calls to it as if it was a local call. logService.log_d() simply passes two strings to the log_d() method that we saw defined in LogService.
10 As mentioned earlier, if we want to pass a Message to the remote method, we have to create a parcel out of it first. This is possible because Message is a parcelable object. We then set it its properties using appropriate setters.
11 Once we have the parcel, we simply call logService.log() and pass it to LogService, where it gets logged.
12 Whenever we make a remote call, it could fail for variety of reasons outside of our control. Because of that, it is a good practice to handle a possible RemoteException.
13 When this activity is about to be destroyed, we ask to unbind the service and free those resources.
At this point our client is complete. There’s a simple UI with a single button that triggers an onClick() call. Once the user clicks the button, our client should be invoking the remote call in the service.

Testing That All Works

Try to run the client from within Eclipse. Since Eclipse knows that LogClient is dependent on LogService, it should install both packages onto your device. Once the client starts, it should bind to the service. Try clicking on the button and check that LogService is indeed logging. Your adb logcat call should give you something like this:

...
I/LogActivity(  613): connected
...
D/LogClient(  554): Hello from onClick() version: 1.0
D/LogClient(  554): Hello from inClick() version 1.1
...
The first line is from LogConnection in the client, indicating that we’ve successfully bound to the service. The other two lines are from the remote service, one for LogService.log_d() and the other one for LogService.log(), where we passed in the Message parcel.

If you run adb shell ps to see the running processes on your device, you’ll notice two separate line items for the client and the server:

app_43    554   33    130684 12748 ffffffff afd0eb08 S com.marakana.logservice
app_42    613   33    132576 16552 ffffffff afd0eb08 S com.marakana.logclient
This indicates that indeed the client and server are two separate applications.

Summary

Android provides an interprocess communication mechanism. It is based on binder: a high-performance, shared-memory system. To create create a remote service, we define it using the Android Interface Definition Language (AIDL), similar to Java interfaces. We then implement the remote interface and connect to it via the IBinder object. This allows us to connect our client to a remote service in a different process altogether.

QUESTION: How to send one class from one process to another process?

Suppose you have a class that you would like to send from one process to another through an IPC interface, you can do that. your class must support the Parcelable interface. Supporting the Parcelable interface is important because it allows the Android system to decompose objects into primitives that can be marshalled across processes.

To create a class that supports the Parcelable protocol, you must do the following:
  1. Make your class implement the Parcelable interface.
  2. Implement writeToParcel, which takes the current state of the object and writes it to a Parcel.
  3. Add a static field called CREATOR to your class which is an object implementing the Parcelable.Creator interface.
  4. Finally, create an .aidl file that declares your parcelable class (as shown for the Rect.aidl file, below). If you are using a custom build process, do not add the .aidl file to your build. Similar to a header file in the C language, this .aidl file isn't compiled.
AIDL uses these methods and fields in the code it generates to marshall and unmarshall your objects.

For example, here is a Rect.aidl file to create a Rect class that's parcelable:
package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
And here is an example of how the Rect class implements the Parcelable protocol.
import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int 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];
        }
    };

    public Rect() {
    }

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

    public void writeToParcel(Parcel out) {
        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();
    }
}
The marshalling in the Rect class is pretty simple. Take a look at the other methods on Parcel to see the other kinds of values you can write to a Parcel.
Warning: Don't forget the security implications of receiving data from other processes. In this case, the Rect reads four numbers from the Parcel, but it is up to you to ensure that these are within the acceptable range of values for whatever the caller is trying to do. See Security and Permissions for more information about how to keep your application secure from malware

2 comments:

  1. For more info about AIDL, read
    http://developer.android.com/guide/components/aidl.html

    ReplyDelete
  2. If service gets crashed how to indicate to UI ie in activity?

    ReplyDelete