Interprocess Communication is the communication of threads across process boundaries. This type of communication is supported through the binder framework in Android. In the article on Services earlier, we discussed Bound Services that has a client-server interface. A bound service is the server which allows clients (components such as activities) to bind to the Service and then send requests and receive responses. The code we discussed works across threads in the same process but will fail in the case of remote services where the Service is running in a different process altogether.
Using a Messenger
If we want this interface to work across different processes, we can use Messenger which’ll allow messages to be passed across process boundaries between client (client component) and server (service). With this technique we can perform interprocess communication (IPC) without the need to use AIDL directly.
Note: A Messenger is coupled with a Handler. Hence, executing all the tasks (passing messages) is sequential by design as the messages are processed on a single thread by the Handler to which it belongs whereas AIDL can execute tasks concurrently on binder threads.
Following is the list of steps involved in this process:
- The Service should implement (contain) a Handler instance that receives a callback (using
handleMessage()for instance) for each call from the client. - The Handler is used to create a
Messengerobject which in itself is actually a reference to the Handler. - The Messenger creates an
IBinder(viagetBinder()) that the Service returns to clients fromonBind(). - Clients use the
IBinderobject in itsServiceConnectionimplementation to instantiate theMessengerobject that references the Service’s Handler which the client uses to sendMessage(withsend()for instance) objects to the Service’s Handler. So the Service receives the messages in itsHandler.handleMessage().
At a higher level the flow is basically like this:
- The Service passes a Messenger reference to the client processes.
- Using that reference, the client sends messages to the server process as many times as it wants to.
- This entire passing/communication mechanism takes place through the Binder framework.
Time to get dirty with some code! Here’s a simple Service class example that uses the Messenger interface:
[java]
public class MessengerService extends Service {
private String TAG = "MessengerService";
// Message codes to check against Message.what
//
// Message.what is a User-defined message code so
// that the recipient can identify what the message is about.
static final int MSG_SAY_HELLO = 1;
// Messenger object used by clients to send messages to IncomingHandler
Messenger mMessenger = new Messenger(new TestHandler());
// Incoming messages Handler
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Bundle bundle = msg.getData();
String hello = (String) bundle.get("hello");
Toast.makeText(getApplicationContext(), hello, Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
public MessengerService() {
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate called");
}
/*
Return our Messenger interface for sending messages to
the service by the clients.
*/
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind done");
return mMessenger.getBinder();
}
@Override
public boolean onUnbind(Intent intent) {
return false;
}
}
[/java]
Now in the client all you need to do is create a Messenger based on the IBinder received in the ServiceConnection implementation and send messages using send(). So here’s an example:
[java]
boolean isBound = false;
Messenger mMessenger;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isBound = true;
// Create the Messenger object
mMessenger = new Messenger(service);
// Create a Message
// Note the usage of MSG_SAY_HELLO as the what value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
// Create a bundle with the data
Bundle bundle = new Bundle();
bundle.putString("hello", "world");
// Set the bundle data to the Message
msg.setData(bundle);
// Send the Message to the Service (in another process)
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
// unbind or process might have crashes
mMessenger = null;
isBound = false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
[/java]
A client component binds to a Service by calling bindService() which leads to calling the onBind() method that returns an IBinder for interacting with the Service. This binding process is asynchronous hence bindService() returns immediately and won’t return the IBinder to the client. To receive IBinder we’ll have to code a ServiceConnection implementation which’ll receive the IBinder in its onServiceConnected() callback method.
Note: Only Activities, Services and Content Providers can bind to a Service. Broadcast receivers cannot do that.
So the entire binding process is implementing a ServiceConnection, binding using bindService() and create a Messenger object out of the IBinder received in onServiceConnected() so that messages can be sent to the Service. When the client component is destroyed, it’ll automatically unbind from the Service but it is always recommended to unbind using unbindService() when you’re done interacting with the Service so that it can shutdown properly while not being used.
Make sure to have this entry in your manifest in order for the Service to work properly in a different process:
[xml]
<service
android:name="com.example.app.MessengerService"
android:process=":my_process">
</service>
[/xml]
So we just saw how to create a Messenger object out of the IBinder received and then send messages to the Service. Wow! We just learnt how to send messages across process boundaries in Android. As you might have noticed, there are no methods to call on the Service by clients but the clients just deliver messages to the Service’s Handler implementation.
Service to Client communication
We saw how to send messages from the Client to the Service, but you can also send messages the other way round leading to a two-way communication/messaging. In order to do this you’ll basically have to create a Messenger object in the Client wrapping a Handler just like we did in the Service class and then send that to our Service in a Message using the replyTo field. Then you can save the reference to the Messenger in your Service and send() Message objects just like we did from Client to Service in our ServiceConnection implementation.
It’s pretty simple but if you’re still confused then there’s some code sample available here and here.
Messenger vs. AIDL
AIDL (Android Interface Definition Language) is basically an IPC mechanism that does the job of decomposing objects into primitives that the operating system can understand and then passing them across processes to perform IPC. Messengers are actually based on AIDL behind the scenes. When using Messenger, it creates a queue of all the client requests that the Service receives one at a time. All this happens on a single thread. In case you want your Service to handle multiple requests simaltaneously then you’ll need to make use of AIDL directly and make sure your Service is capable of multi-threading and also ensure thread-safety.
It is generally not recommended to use AIDL directly as it is a really complicated implementation and you need to ensure thread safety and handle multi threading.
Conclusion
We learnt how easy it is to achieve IPC using a Messenger with Services in Android. In the next article we’ll go through AIDL that you should use directly only if you’re certain that you must use it.