Android Bound Service: IPC with Messenger
In this post, we want to talk about Android Bound Service. A bound service is a service that allows other android components (like activity) to bind to it and send and receive data. In the previous post, we talked about local service and we saw how we can create a service, start and stop it. A bound service is a service that can be used not only by components running in the same process as local service, but activities and services, running in different processes, can bind to it and send and receive data. When we implement a bound service we have always to extend Service class but we have to override onBind
method too. This method returns an object that implements IBinder
, that can be used to interact with the service.
There are three way we can create a bound service:
- Extending IBinder interface
- Using Messenger
- Using AIDL
In this post we want to analyze how to create a Android service with Messenger. Using this method, we can create a service that can be used by components in different processes. In this case, we use Handler and Message to exchange data between service and other components.
Implementing bound service with Messenger
Service based on Messenger can communicate with other components in different processes, known as Inter Process Communication (IPC), without using AIDL. To implement a service like this we need:
- A service handler: this component handles incoming requests from clients that interact with the service itself.
- A Messenger: this class is used to create an object implementing
IBinder
interface so that a client can interact with the service.
So let’s implement the Service. As example we can suppose we want to create a service that receives a string and converts it in upper-case and returns the result to the client.
So as first thing, we create a class that implements Service
:
public class ConvertService extends Service { .. }
As told before, we need an Handler to implement incoming request from clients so, we can create an inner class like this:
class ConvertHanlder extends Handler { @Override public void handleMessage(Message msg) { // This is the action int msgType = msg.what; switch(msgType) { case TO_UPPER_CASE: { try { // Incoming data String data = msg.getData().getString("data"); Message resp = Message.obtain(null, TO_UPPER_CASE_RESPONSE); Bundle bResp = new Bundle(); bResp.putString("respData", data.toUpperCase()); resp.setData(bResp); msg.replyTo.send(resp); } catch (RemoteException e) { e.printStackTrace(); } break; } default: super.handleMessage(msg); } }
In handleMessage we start handling the incoming requests. The first thing we have to do it “decode” the type of request we are handling. We can use for this purpose the what
attribute of Message class. Depending on its value we perform different operations: in our case we just convert in upper case a string. We pass the string value a Bundle attached to the Message, so that at line 12 we get the value. We have to send a response to the client, so we create another Message (line 13) that holds the response and attach a new Bundle holding the converted string (line 14-15). At line 16 we send the message back to the client (we will see it later).
So in this way, we created our request handler but we have to create an IBinder instance so that a client can use our service. To do it, we need a Messenger:
public class ConvertService extends Service { ... private Messenger msg = new Messenger(new ConvertHanlder());; ... @Override public IBinder onBind(Intent arg0) { return msg.getBinder(); } }
At line 3, a new instance of Messenger class is created passing the Handler we discussed before. At line 6 we override the onBind
method and return an instance of IBinder interface. Our Service is ready. At the end we define it in Manifest.xml:
<service android:name=".ConvertService" android:process=":convertprc"/>
Notice we used android:process
so that the Service runs in a different process from the client.
Android Service client
Now we have to implement a client that binds to the service and send data to it. We can suppose that the client is an Activity that allows the user to insert a string that has to be converted in uppercase. The activity calls bindService
method to bind to the service created before. When we bind to a “remote” service, using bindService method, we need to provide a callback methods so that we get notified when the bind process is completed and we can “use” the service. We have to create a class that implements ServiceConnection to receive this notification:
.. private ServiceConnection sConn; private Messenger messenger; .. @Override protected void onCreate(Bundle savedInstanceState) { // Service Connection to handle system callbacks sConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { messenger = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { // We are conntected to the service messenger = new Messenger(service); } }; ... // We bind to the service bindService(new Intent(this, ConvertService.class), sConn, Context.BIND_AUTO_CREATE); .. }
At line 8, we create an instance of ServiceConnection override its methods. At line 18 we create a Messanger that we use, later, to get the IBinder instance so that we can send the messages to our service. Finally at line 24 we bind to the service specifying the service class and the callback interface
Now we need a “receiving” handler to manage the service response:
// This class handles the Service response class ResponseHandler extends Handler { @Override public void handleMessage(Message msg) { int respCode = msg.what; switch (respCode) { case ConvertService.TO_UPPER_CASE_RESPONSE: { result = msg.getData().getString("respData"); txtResult.setText(result); } } } }
This handler behaves like the one in the service implementation, it extracts the response from the Bundle attached to the Message and show the result to the user at line 11.
The last thing we have to cover is sending from the Activity to the Service the string that has to be converted. In this case we can suppose we have a Button in our interface that when user clicks it, the Activity sends the data:
btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String val = edt.getText().toString(); Message msg = Message .obtain(null, ConvertService.TO_UPPER_CASE); msg.replyTo = new Messenger(new ResponseHandler()); // We pass the value Bundle b = new Bundle(); b.putString("data", val); msg.setData(b); try { messenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } });
Please notice that at line 9 we set the reply Messenger that will be used the the service when it has to send back the response.