Android IPC - Getting the Most Out of Components - Android Programming: Pushing the Limits (2014)

Android Programming: Pushing the Limits (2014)

Part II. Getting the Most Out of Components

Chapter 7. Android IPC

Android has a powerful feature that’s capable of communicating between two different applications. You can

set up this communication in many ways in your code, but there is one central mechanism behind the scenes

that handles all inter-process communication, the Binder IPC ( I nter- P rocess C ommunication).

The Binder in Android has a long history. It was originally developed as the OpenBinder at Be Inc. for the Be

Operating System (BeOS) under the leadership of Dianne Hackborn. It was ported and later rewritten for

Android in order to support IPC for applications. Basically, the Binder provides the features for binding functions

and data between one execution environment and another. Because each Android application runs in its own

Dalvik VM, which is an isolated execution environment, the Binder is very suitable for this purpose.

Back in 2009, there was a long debate in the Linux community about why Google chose to use

the Binder for IPC rather than the existing solution in the Linux kernel named dbus. The simplest

explanation is likely that Dianne Hackborn, one of the lead Android framework engineers, was also

the one leading the development of the OpenBinder at Be Inc. When Android was first developed,

this was their best choice for IPC, and today it’s an integral part of the Android system. The dbus

mechanism from Linux is also used on many Android devices, specifically for communication with the

Radio Interface Layer (RIL) and for Bluetooth up until Android 4.3. However, most IPC calls on Android

go through the Binder.

In addition to being used for communication between Android applications, the Binder is actually essential in

order for an application to communicate with the Android system. When you retrieve a system Service using

the Context.getSystemService() method, the Binder is working behind the scenes to provide your

application with a wrapper for a Service object. The Binder isn’t just used by Services, it also handles all

communication between Android components and the Android system.

Normally, an Android application doesn’t have to contend with the low-level details of the Binder because the

Android APIs provide nice wrappers that make it easy to perform IPC. In this chapter, I describe how the Binder

works and provide a few examples that show you how to build remote APIs for other applications.

The Binder Explained

As I mentioned in the introduction, the Binder in Android was originally designed under the name “OpenBinder

for BeOS” and not Linux, which is the kernel Android runs on. In earlier versions of Android, essentially the same

code used for OpenBinder was used for the Linux kernel driver that implemented the Binder for Android. This

was less than optimal because the architecture from BeOS is very different from the architecture found in Linux.

In later versions of Android, Google rewrote the implementation so that now it’s better suited for the Linux

kernel architecture.

When two applications communicate using the Binder IPC, they’re using this kernel driver to relay messages

(see Figure 7-1) between them. Besides the messaging function, the Binder provides additional functions such

as identifying the remote caller (process ID and user ID) and notifying when a remote process dies (called link

to death).

Conceptual

function call

Application 1

Application 2

Binder

communication

path

Binder Driver

Linux Kernel

Figure 7-1 Simple diagram illustrating communication

using the Binder IPC

For example, the system uses these additional functions in the Binder when the system Service, which

manages all windows in Android through the WindowManager, keeps a Binder reference to every

application and is notified through a link-to-death notification when an application’s window closes.

Communication using the Binder follows the client-server model. Clients use a client-side proxy to handle the

communication with the kernel driver. On the server-side, the Binder framework maintains a number of Binder

threads. The kernel driver delivers the messages from the client-side proxy to the receiving object using one

of the Binder threads on the server-side. This is important to remember because when you receive calls to a

Service through the Binder, they will not be executed on the main thread of your application. That way, a

client to a remote Service cannot block the Service application’s main thread.

You implement the Binder in Android by using the base class Binder and the interface IBinder. As I show

in Chapter 6, a Service can return a class implementing the IBinder interface in the method Service.

onBind(). When the Service publishes a remote API, you generally use an AIDL file to generate this

IBinder class, but as I describe next, other methods are available as well.

Binder Address

Communicating over the Binder requires that the client know the address of the remote Binder object.

However, the design of the Binder is such that only the implementation, like the Service you want to call,

knows its address. You address on the Android API level by using Intent resolution. The client constructs

an Intent object, using either an action String or a ComponentName and then uses that to initiate

communication with the remote application. However, an Intent is only an abstraction of the actual Binder

address and needs to be translated in order to set up the communication.

A special Binder node called ServiceManager that is running inside the Android system server manages

all address resolution in Android. This is the only Binder node that has a globally known address. Because all

components in Android use the Binder for communication, they need to register using the ServiceManager,

which they reach through the well-known address (see Figure 7-2).

3. Service communication

Service

Client

1. addService()

1. getService()

Service

Manager

Figure 7-2 Diagram showing service registration and

lookup through the ServiceManager

Clients that want to communicate with a Service or other component query the ServiceManager,

implicitly through the Intent resolution, to receive the Binder address.

Binder Transactions

When one process sends data to another in Android, it’s called a transaction. You start transactions on the

Binder by calling IBinder.transact() on the client, and the Service receives the call on the method

Binder.onTransact(), as shown here:

public String performCustomBinderTransaction(IBinder binder, String arg0,

int arg1, float arg2)

throws RemoteException {

Parcel request = Parcel.obtain();

Parcel response = Parcel.obtain();

// Populate request data...

request.writeString(arg0);

request.writeInt(arg1);

request.writeFloat(arg2);

// Perform transaction

binder.transact(IBinder.FIRST_CALL_TRANSACTION, request, response, 0);

// Read the result from the response…

String result = response.readString();

// Recycle the objects

request.recycle();

response.recycle();

return result;

}

The method in the preceding example illustrates how from the client-side, once you have a valid IBinder reference,

you can perform a custom Binder transaction toward the Service. I explain the Parcel objects in detail in the

following example. They are used as simple data containers for the data you want to include in the transaction.

public class CustomBinder extends Binder {

@Override

protected boolean onTransact(int code, Parcel request,

Parcel response, int flags)

throws RemoteException {

// Read the data in the request

String arg0 = request.readString();

int arg1 = request.readInt();

float arg2 = request.readFloat();

String result = buildResult(arg0, arg1, arg2);

// Write the result to the response Parcel

response.writeString(result);

// Return true on success

return true;

}

private String buildResult(String arg0, int arg1, float arg2) {

String result = null;

// TODO Build the result

return result;

}

}

If you implement a custom Binder object in your Service without using an AIDL, you need to implement

the method Binder.onTransact() as just shown. Here you simply respond to the incoming transaction by

populating the second Parcel object with the relevant data.

The result is a synchronized two-way call through the Binder IPC. You can also perform a one-way call from the

client by setting the flag in the IBinder.transact() call to FLAG_ONEWAY, in which case, you can leave

the second Parcel argument as null. Doing so provides better performance for this call because it needs to

marshal and unmarshal only one Parcel object.

Using this low-level way of performing transactions between two applications is not recommended if you

intend to publish an API for other developers to use. However, when you need fine-grained control of how

data is sent between two applications, this can be an efficient method to use. I share it here to illustrate how

the Binder works on its basic level. Most of the time, you’ll use either AIDL or a Messenger as described in the

“Messenger” section, later in this chapter.

Parcel

A Binder transaction will usually carry some transaction data, as shown in the previous example. This data is

called a parcel, and there is an API for developers, which allows you to create a parcel for most Java objects.

You can compare parcels in Android with serializable objects in Java SE. The difference is that you need to

implement the marshaling and unmarshaling of objects yourself using the Parcelable interface. This

interface defines two methods you need to implement for writing an object to a Parcel and also a static

final Creator object that implements the code for reading the object from a Parcel, as shown here:

public class CustomData implements Parcelable {

public static final Parcelable.Creator<CustomData> CREATOR

= new Parcelable.Creator<CustomData>() {

@Override

public CustomData createFromParcel(Parcel parcel) {

CustomData customData = new CustomData();

customData.mName = parcel.readString();

customData.mReferences = new ArrayList<String>();

parcel.readStringList(customData.mReferences);

customData.mCreated = new Date(parcel.readLong());

return customData;

}

@Override

public CustomData[] newArray(int size) {

return new CustomData[size];

}

};

private String mName;

private List<String> mReferences;

private Date mCreated;

public CustomData() {

mName = “”; // Defaults to empty string

mReferences = new ArrayList<String>();

mCreated = new Date(); // Defaults to now

}

@Override

public int describeContents() {

return 0;

}

@Override

public void writeToParcel(Parcel parcel, int flags) {

parcel.writeString(mName);

parcel.writeStringList(mReferences);

parcel.writeLong(mCreated.getTime());

}

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

CustomData that = (CustomData) o;

return mCreated.equals(that.mCreated) && mName.equals(that.mName);

}

@Override

public int hashCode() {

int result = mName.hashCode();

result = 31 * result + mCreated.hashCode();

return result;

}

}

The preceding code shows an object that implements the Parcelable interface. Note the implementation of

the CREATOR field and how the createFromParcel() method uses the Parcel.readStringList()

method to read the entire List object without having to specify how long the list is (this is handled internally

by the Parcel object).

After you implement this interface, you can send objects of this class between applications through the Binder

IPC.

Link to Death

Another feature of the Binder in Android is that it allows clients to be notified when a Service is terminated.

As I mentioned earlier, this is called link to death, and it’s implemented through the Binder method IBinder.

linkToDeath(). When a client receives an IBinder object in the onServiceConnected() method, the

client can call linkToDeath() with a callback implementing the interface IBinder.DeathRecipient.

Because Android applications can be killed by the system when it’s running low on resources (available RAM,

and so on), it can be useful to register for these notifications in a client in case it wants to be notified when the

remote side is terminated. The following code shows how to register for link to death once you receive a valid

IBinder reference:

public class LinkToDeathSample extends Service {

private static final String TAG = “LinkToDeathSample”;

// Service methods exclude for brevity...

private void notifyRemoteServiceDeath(IBinder iBinder) {

try {

iBinder.linkToDeath(new MyLinkToDeathCallback(), 0);

} catch (RemoteException e) {

Log.e(TAG, “Error registering for link to death.”, e);

}

}

class MyLinkToDeathCallback implements IBinder.DeathRecipient {

@Override

public void binderDied() {

// TODO Handle death of remote binder...

}

}

}

You can also check whether the process for the remote Binder is still alive by calling IBinder.

pingBinder(). If that call returns true, the process is alive and ready.

If you’re binding to a Service, this method is not necessary because you’ll always have the

ServiceConnection.onServiceDisconnected() callback to notify you when you lose your

connection. However, if you received a Binder object some other way, this method can be useful.

Designing APIs

Most applications will rarely need to implement an API for third-party applications because doing so is outside

the scope of their features. However, it does become relevant with the types of applications that provide a plug-

in mechanism. If you search for “plugin” at the Google Play Store, you’ll find tons of examples of these types of

applications. If your application fits this category, you’ll probably benefit from preparing an API for third-party

applications.

An API for third-party applications can be either implemented as a Service or as a ContentProvider. In

this section, I describe how to do so using a Service; I show how to use a ContentProvider in Chapter 9.

When implementing an API, you need to consider a number of things. Do you need to handle concurrent

requests, or is it enough to process one client request at a time? Will you publish only one or very few

operations, or is it a more complex set of API methods that clients can use? The answer to these questions will

determine the most appropriate method for implementing your remote API.

Another detail to consider is whether you’ll be sharing this API with other developers or if it will be used only by

your own applications (that is, only you will be publishing plug-ins). In the first case, consider building a library

project that wraps the client-side implementation in an easy-to-use Java API. If you’re the only user of the API, it

is probably safe to use either the AIDL or the Messenger directly as described in the next two sections.

If it’s enough that your API is one-way, you’re probably fine with using an IntentService as I describe in

Chapter 6. In that case, you just add the necessary permissions and make sure that the API is exported in the

manifest.

AIDL

In software engineering, the term Interface Definition Language (IDL) has become the generic term for a

specification language that describes the interface for a software component. In Android, the IDL is called

Android Interface Definition Language (AIDL) and is written in text files with a Java-like syntax. However, you

need to consider a number of differences between writing AIDL files and writing a Java interface.

First, for all non-primitive parameters, you need to specify one of three directional types: in, out, or inout.

The in type indicates that they are used only for input and that your client won’t see any changes that the

Service does to this object. The out type indicates that the input object contains no relevant data but will

be populated with data by the Service that’s relevant in the response from the method. The inout type

is a combination of both types. It’s very important to use only the type that’s needed because there’s a cost

associated with each type.

Another thing to remember is that for all custom classes used in communication, you need to create an AIDL file

that declares your class as a Parcelable.

The following code snippet is an example of an AIDL file with the name CustomData.aidl. It should be

placed in the same package as the Java class source file.

package com.aptl.sampleapi;

parcelable CustomData;

Finally, all custom classes you need for your API must be imported in the AIDL file for the API, as shown here:

package com.aptl.sampleapi;

import com.aptl.sampleapi.CustomData;

interface ApiInterfaceV1 {

/**

* Simple remote method for checking if a number is a prime.

*/

boolean isPrime(long value);

/**

* Retrieve all CustomData objects since timestamp.

* Will get at most result.length objects.

*/

void getAllDataSince(long timestamp, out CustomData[] result);

/**

* Stores the CustomData object.

*/

void storeData(in CustomData data);

}

This is an example of an AIDL file with three methods. Note: Primitives don’t need a directional tag (they’re

always called by value).

Remember, after you’ve implemented a client, you cannot change or remove any methods that you’ve put in

an AIDL. You can add new methods at the end of the file, but because of the way the AIDL compiler generates

the identifier for each method, you cannot change any of the existing methods without breaking backward

compatibility. When handling new versions of the API, the recommended way is to create a new AIDL file with

the new or changed methods. Doing so allows you to maintain backward compatibility with older clients. As

you can see by the name of the preceding AIDL file, you handle versioning of AIDL by appending V1 for the first

version of the file. When you add new methods to the API, you create a file by ending with V2, and so on.

This method for versioning is one of the drawbacks with using AIDL files. One way to manage this issue is to

provide a Java wrapper around the AIDL and you publish this either as a library project or as a JAR file that

developers can use. This way, a client won’t have to implement multiple AIDLs but can always download the

latest version of your wrapper and be sure that it’s compatible. I show an example of how to create such a

wrapper in section “Wrapping APIs with Library Projects,” later in this chapter.

When you have an AIDL file ready, you need to implement it on both the service-side and the client-side, as

shown here:

public class AidlService extends Service {

private ArrayList<CustomData> mCustomDataCollection;

@Override

public void onCreate() {

super.onCreate();

mCustomDataCollection = new ArrayList<CustomData>();

// TODO Populate the list with stored values...

}

public IBinder onBind(Intent intent) {

return mBinder;

}

public static boolean isPrimeImpl(long number) {

// Implementation left out for brevity...

return false;

}

private void getDataSinceImpl(CustomData[] result, Date since) {

int size = mCustomDataCollection.size();

int pos = 0;

for(int i = 0; i < size && pos < result.length; i++) {

CustomData storedValue = mCustomDataCollection.get(i);

if(since.after(storedValue.getCreated())) {

result[pos++] = storedValue;

}

}

}

private void storeDataImpl(CustomData data) {

int size = mCustomDataCollection.size();

for (int i = 0; i < size; i++) {

CustomData customData = mCustomDataCollection.get(i);

if(customData.equals(data)) {

mCustomDataCollection.set(i, data);

return;

}

}

mCustomDataCollection.add(data);

}

private final ApiInterfaceV1.Stub mBinder

= new ApiInterfaceV1.Stub() {

@Override

public boolean isPrime(long value) throws RemoteException {

return isPrimeImpl(value);

}

@Override

public void getAllDataSince(long timestamp, CustomData[] result)

throws RemoteException {

getDataSinceImpl(result, new Date(timestamp));

}

@Override

public void storeData(CustomData data) throws RemoteException {

storeDataImpl(data);

}

};

}

The preceding example shows the implementation of the AIDL stub in the end on the Service. This object is

also what is returned to clients that bind to the Service in the onBind() method. Note that each call to the

API in the Service will be running on its own thread because the Binder provides a pool of threads on which

it executes calls from clients. This means that a client cannot block the main thread of the Service it is calling

when you’re using this method.

The following Activity shows how to bind to a remote Service and retrieve the interface for

ApiInterfaceV1. This is the preferred solution if you’re the sole user of the remote API and can manage the

versioning on both sides (or on the same development team).

public class MyApiClient extends Activity implements ServiceConnection {

private ApiInterfaceV1 mService;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}

@Override

protected void onResume() {

super.onResume();

bindService(new Intent(“com.aptl.sampleapi.AIDL_SERVICE”),

this, BIND_AUTO_CREATE);

}

public void onCheckForPrime(View view) {

EditText numberToCheck = (EditText) findViewById(R.id.number_

input);

long number = Long.valueOf(numberToCheck.getText().toString());

boolean isPrime = mService.isPrime(number);

String message = isPrime ?

getString(R.string.number_is_prime, number)

: getString(R.string.number_not_prime, number);

Toast.makeText(this, message, Toast.LENGTH_SHORT).show();

}

@Override

protected void onPause() {

super.onPause();

unbindService(this);

}

@Override

public void onServiceConnected(ComponentName componentName,

IBinder iBinder) {

mService = ApiInterfaceV1.Stub.asInterface(iBinder);

}

@Override

public void onServiceDisconnected(ComponentName componentName) {

mService = null;

}

}

Callbacks with AIDL

Clients can also implement an AIDL that can be used as a callback interface by the Service, which is useful

if you want to register clients to receive callbacks when something happens on the Service—for instance,

when data is updated from an online server that the Service is communicating with.

In the following example, you can see the new AIDL file for the callback interface, note the keyword oneway

that tells the AIDL compiler that this interface is only a one-way communication. No response back to the caller,

in this case the Service, is needed. This will give you a slight performance boost.

package com.aptl.sampleapi;

import com.aptl.sampleapi.CustomData;

oneway interface AidlCallback {

void onDataUpdated(in CustomData[] data);

}

Next, you create an instance of this interface in your client, shown as follows. In this case, you simply show a

Toast when you receive a callback from the Service:

private AidlCallback.Stub mAidlCallback = new AidlCallback.Stub() {

@Override

public void onDataUpdated(CustomData[] data) throws RemoteException {

Toast.makeText(MyApiClient.this, “Data was updated!”,

Toast.LENGTH_SHORT).show();

}

};

In the AIDL for the Service shown earlier, you add one more line for registering the callback:

void addCallback(in AidlCallback callback);

Finally, you implement the addCallback() method on the Service. Here, you also use the

linkToDeath() method to receive a notification in case the client Binder died.

@Override

public void addCallback(final AidlCallback callback) throws

RemoteException {

mCallbacks.add(callback);

callback.asBinder().linkToDeath(new DeathRecipient() {

@Override

public void binderDied() {

mCallbacks.remove(callback);

}

}, 0);

}

Normally, you should have both an addCallback() and a removeCallback() method, but I’m

leaving that as an exercise for you to explore.

The previous example shows how to create callback interfaces between applications. It also shows how

you can transfer a Binder object between two applications without having to register it through the

ServiceManager. Because only the client and the Service know the address for this Binder, it can

effectively be used as a security mechanism when doing IPC.

Messenger

Another way of providing a remote interface is through the Messenger class. This class is useful when you

have a Service where you don’t need to support concurrent operations to clients. The Messenger class uses

a Handler to execute each incoming message, so all client calls will run on the same thread in serial order. You

also get rid of the problems with AIDL files and can more easily provide an asynchronous message-based API

for clients. Although not as powerful, this class can be more efficient at times because you’ll get much easier

implementation, both for clients and Services.

The following example shows how to use the Messenger class to provide an asynchronous API. The

onBind() method returns the Binder object from the Messenger created in onCreate(). When

the Messenger receives a message, it can reply to the client using a Messenger object stored in the

replyTo field.

public class MessengerService extends Service {

private Handler mMessageHandler;

private Messenger mMessenger;

@Override

public void onCreate() {

super.onCreate();

HandlerThread handlerThread = new HandlerThread(“MessengerService”);

handlerThread.start();

mMessageHandler = new Handler(handlerThread.getLooper(),

new MyHandlerCallback());

mMessenger = new Messenger(mMessageHandler);

}

public IBinder onBind(Intent intent) {

return mMessenger.getBinder();

}

@Override

public void onDestroy() {

super.onDestroy();

mMessageHandler.getLooper().quit();

}

private class MyHandlerCallback implements Handler.Callback {

@Override

public boolean handleMessage(Message message) {

boolean delivered = false;

switch (message.what) {

case MessageAPI.SEND_TEXT_MSG:

delivered = sendTextMessage((String) message.obj);

break;

case MessageAPI.SEND_PHOTO_MSG:

delivered = sendPhotoMessage((Bitmap) message.obj);

break;

}

Message reply = Message.obtain(null,

MessageAPI.MESSAGE_DELIVERED_MSG,

delivered);

try {

message.replyTo.send(reply);

} catch (RemoteException e) {

Log.e(“MessengerService”,

“Error sending message reply!”, e);

}

return true;

}

}

// Return true when delivered

private boolean sendPhotoMessage(Bitmap photo) {

// Implementation left out for brevity

return true;

}

// Return true when delivered

private boolean sendTextMessage(String textMessage) {

// Implementation left out for brevity

return true;

}

}

The following example shows a client that first binds to the Service and then constructs a new Messenger

object with the IBinder as a parameter. This now acts as a proxy for the Messenger running in the remote

Service. When you send a message to the Service, you can also set the replyTo field of the Message object.

public class MyMessengerClient extends Activity

implements ServiceConnection {

private ApiInterfaceV1 mService;

private Messenger mRemoteMessenger;

private Messenger mReplyMessenger;

private Handler mReplyHandler;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

HandlerThread handlerThread = new HandlerThread(“ReplyMessenger”);

handlerThread.start();

mReplyHandler = new Handler(handlerThread.getLooper(),

new ReplyHandlerCallback())

mReplyMessenger = new Messenger(mReplyHandler);

}

@Override

protected void onResume() {

super.onResume();

bindService(new Intent(“com.aptl.sampleapi.MESSENGER_SERVICE”),

this, BIND_AUTO_CREATE);

}

public void onSendTextPressed(View view) {

String textMessage = ((EditText) findViewById(R.id.message_input))

.getText().toString();

Message message = Message.obtain();

message.what = MessageAPI.SEND_TEXT_MSG;

message.obj = textMessage;

message.replyTo = mReplyMessenger;

try {

mRemoteMessenger.send(message);

} catch (RemoteException e) {

// Remote service is dead...

}

}

@Override

protected void onPause() {

super.onPause();

unbindService(this);

}

@Override

protected void onDestroy() {

super.onDestroy();

mReplyHandler.getLooper().quit();

}

@Override

public void onServiceConnected(ComponentName componentName,

IBinder iBinder) {

mRemoteMessenger = new Messenger(iBinder);

}

@Override

public void onServiceDisconnected(ComponentName componentName) {

mRemoteMessenger = null;

}

private class ReplyHandlerCallback implements Handler.Callback {

@Override

public boolean handleMessage(Message message) {

switch (message.what) {

case MessageAPI.MESSAGE_DELIVERED_MSG:

// TODO Handle async reply from service

break;

}

return true;

}

}

}

This method is very similar to using the IntentService as I describe in Chapter 6, but instead of working

with Intent objects, here you’re utilizing the Message class used for triggering operations on a Handler,

as I describe in Chapter 2. Also, using a Messenger provides a convenient way of implementing asynchronous

communication without having to use BroadcastReceivers.

Wrapping APIs with Library Projects

Regardless of whether you use AIDL or the Messenger class to implement your remote API, it’s a good idea to

extract all the API-specific classes and interfaces to a library project and create a pure Java wrapper for clients

to use. Because you probably want to support your complex objects in your API, providing only an AIDL file for

your API is usually not enough. You also need to provide these custom classes to clients. As I describe in Chapter

1, when it comes to distribution and versioning, setting up an Android library project for your API is a simple

and efficient way of handling all the problems related to remote APIs. You can also package the compiled

wrapper code into a JAR file that is easily distributed as a third-party library. I recommend using an Android

library project, uploading it to an online version control service like GitHub, and letting other developers simply

use that code to integrate with your application.

The easiest way to set up a library project for your remote API is to move all AIDL files and Parcelable classes

to a library project that you reference in the application that implements your remote API. However, if you have

several AIDLs (new versions, client callbacks, and so on), it can easily become quite complicated, so it’s also a

good practice to wrap everything in a more easy-to-use Java class, as shown here:

public class ApiWrapper {

private Context mContext;

private ApiCallback mCallback;

private MyServiceConnectionV1 mServiceConnection =

new MyServiceConnectionV1();

private ApiInterfaceV1 mServiceV1;

public void release() {

mContext.unbindService(mServiceConnection);

}

public ApiWrapper(Context context, ApiCallback callback) {

mContext = context;

mCallback = callback;

mContext.bindService(new Intent(“com.aptl.sampleapi.AIDL_

SERVICE”),

mServiceConnection, Context.BIND_AUTO_CREATE);

}

public void getAllDataSince(long timestamp, CustomData[] result) {

if (mServiceV1 != null) {

try {

mServiceV1.getAllDataSince(timestamp, result);

} catch (RemoteException e) {

// TODO Handle service error

}

}

}

void storeData(CustomData data) {

if (mServiceV1 != null) {

try {

mServiceV1.storeData(data);

} catch (RemoteException e) {

// Handle service error

}

}

}

private class MyServiceConnectionV1 implements ServiceConnection {

@Override

public void onServiceConnected(ComponentName componentName,

IBinder iBinder) {

mServiceV1 = ApiInterfaceV1.Stub.asInterface(iBinder);

try {

mServiceV1.setCallback(mAidlCallback);

} catch (RemoteException e) {

// Handle service error...

}

mCallback.onApiReady(ApiWrapper.this);

}

@Override

public void onServiceDisconnected(ComponentName componentName) {

mServiceV1 = null;

if(mCallback != null) {

mCallback.onApiLost();

}

}

}

private AidlCallback.Stub mAidlCallback = new AidlCallback.Stub() {

@Override

public void onDataUpdated(CustomData[] data)

throws RemoteException {

if(mCallback != null) {

mCallback.onDataUpdated(data);

}

}

};

public interface ApiCallback {

void onApiReady(ApiWrapper apiWrapper);

void onApiLost();

void onDataUpdated(CustomData[] data);

}

}

The preceding code shows how to create a wrapper for the AIDL examples shown earlier in this chapter. This

method creates a much easier interface for your Service to the client. You can even manage AIDL callbacks by

wrapping them in ordinary Java interfaces as shown with the preceding ApiCallback.

This method lets you use the standard Java approach for version-controlling your API. You can add the @

deprecated tag to methods, and you can add new methods to your wrapper that handle the versioning

of the API behind the scenes. Clients will not have to worry about these details, and you can easily maintain

backward compatibility.

You can implement different versions of your API on the Service by returning different IBinder objects

depending on the contents of the Intent used in Context.bindService(), as shown here:

public IBinder onBind(Intent intent) {

int apiVersionRequested = intent.getIntExtra(EXTRA_VERSION_TAG, 1);

switch (apiVersionRequested) {

case 1:

return mBinderV1;

case 2:

return mBinderV2;

case 3:

return mBinderV3;

default:

return null;

}

}

The preceding example shows how you can retrieve an int from the Intent to decide which version of the

API to return. This method allows you to create new AIDL files for updates to your API. Your wrapper will now

bind to each version and keep one local reference for every binding.

Securing Remote APIs

Security should always be a priority when you’re designing Android applications, regardless of what you’re

doing. When providing APIs between applications security becomes even more important. (I go into security

for Android applications in more detail in Chapter 12.) Luckily, securing your published Services, and other

components, is quite easy, as shown here:

<?xml version=”1.0” encoding=”utf-8”?>

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

package=”com.aptl.sampleapi”>

<permission android:name=”com.aptl.sampleapi.CALL_SERVICE”

android:protectionLevel=”normal”/>

<uses-sdk

android:minSdkVersion=”17”

android:targetSdkVersion=”17”/>

<application

android:icon=”@drawable/icon”

android:label=”@string/app_name”>

<service

android:name=”.AidlService”

android:exported=”true”

android:permission=”com.aptl.sampleapi.CALL_SERVICE” >

<intent-filter>

<action android:name=”com.aptl.sampleapi.AIDL_SERVICE”/>

</intent-filter>

</service>

</application>

</manifest>

This XML is an example of how the AndroidManifest.xml file might appear for a Service that you

publish. The important areas are shown in bold. First, you need to set the attribute android:exported to

true. The default value for this attribute depends on how you define the intent-filter for the Service.

If you don’t include an intent-filter, the Service is only for internal use (addressed through its

component name), and it won’t be exported. If you define an intent-filter, the Service is exported

by default. I highly recommend that you always define this attribute and set the value according to your needs,

whether or not it’s an exported Service.

If you’re exporting a Service, the most important part is to set up permissions. I go into detail about defining

permissions in Chapter 12, but the previous example shows the simplest form. You define the permission above

the application tag and give it a protectionLevel. Next, you set the android:permission attribute for

the Service to declare that clients for this Service must declare this permission in their manifest.

It’s usually enough to declare permissions as shown in the previous code block, but sometimes you need to go

beyond Android’s permission management. In Chapter 12, I discuss more advanced methods for securing your

application that also apply to APIs that you’ll publish.

Summary

In this chapter, you discovered how to use the Service component in Android to provide a remote API for

other applications to use. You are now familiar with how the Binder IPC works in Android and the choices you

have when it comes to implementing a remote API.

As I discussed, AIDL is a powerful but complicated method that requires more consideration when designing. It

allows you to do normal synchronous Java method calls across applications in different processes, but you need

to carefully consider how to design your API and think about versioning.

Also, using the Messenger class is an easy way to create an asynchronous remote API, but it’s also limited

because all client calls will be running on a single thread, as opposed to the AIDL approach where you have one

thread for every client. That said, the message-based approach will usually perform better than AIDL, so many

times this approach is preferable.

In addition, I recommended that you provide an Android library project that wraps your remote API in a more

easy-to-use set of Java classes, especially if you will use the AIDL approach. Doing so also makes it easier for you

to handle new versions of the API while maintaining backward-compatibility with older clients.

Finally, pay extra attention to securing your remote API. Declare permissions properly and make sure that only

the components that should be published have the android:exported flag set to true in the manifest.

Further Resources Websites

“Android Interprocess Communication” by Thorsten Schreiber at www.nds.rub.de/media/attachments/files/2012/03/binder.pdf

“Android IPC Mechanism” by Jim Huang at http://0xlab.org/~jserv/android-binder-ipc.pdf

“Deep Dive into Android IPC/Binder Framework” by Aleksandar Gargenta at http://marakana.com/s/post/1340/Deep_Dive_Into_Binder_Presentation.htm

Android Developers Blog. “Service API Changes Starting with Android 2.0” by Dianne Hackborn at

http://android-developers.blogspot.se/2010/02/service-api-changes-starting-with.html

A summary of using Binder by Dianne Hackborn at https://lkml.org/lkml/2009/6/25/3