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