There are different ways to communicate with a Service. A commonly used approach is to use Intents where the Service can respond according to the intent action. This is easily implemented but if the Service is providing many different operations the resulting code might become complex which will result in a hardly maintainable code. Furthermore, when using Intents the developer always has to take care of adding the parameters to the intent and after the result intent was received the result-parameters have to be retrieved from the intent. This will result in an increased programming overhead within the client each time a remote functionality is called which can lead to error-prone code. To avoid these disadvantages you can use the built-in Remote Procedure Call (RPC) mechanism of Android. To demonstrate the use of RPCs in Android I’ve created a basic example. This example consists of two apps where the first contains a Service and the second an Activity. The Activity will connect to the Service using the Android RPC mechanism and will query a String from that Service. The Service and the Activity contain Log output so that you can follow each step of the communication process during an RPC call (using adb logcat). As always you can browse or download the source code of the example App at our SVN repository at Google Code (http://code.google.com/p/android-aidl-ipc-rpc-example/) or use svn to check the project out and your allowed to reuse portions of this code as you wish.
The basic concept of RPCs in Android
In Android a Service can export multiple remote interfaces. These remote Interfaces offer functionality which can be used from clients (e.g. other activities or services).
To describe the methods which can be remotely called Android is using the Android Interface Definition Language (AIDL). The format of the AIDL is quite similar to the java interface syntax and all parameters can be either basic java data types or Parcelables. The compiler will automatically generate an abstract Stub class and an interface out of this description file. Because this post is supposed to be a basic example of RPC in Android I won’t explain how to use Parcelable parameters but I might extend the example with another posting later on.
If a client wants to call methods in a remote service it can just call the bindService –method which is defined within the android.content.Context and is therefore available in Activities and Services. The method is declared as followed:
1 | public boolean bindService (Intent service, ServiceConnection conn, int flags) |
To bind a remote service we must define these three parameters:
A call to the bindService method will establish a connection to the service asynchronously and the callback within the ServiceConnection will be called once the remote interface was returned by the Service. Within this remote interface all the functionality provided by the service is and it can be used by the client like a local object. This allows the client to easily call multiple methods in the service without the need of always rethinking about the fact that it is a remote service.
The only point where additional attention is required is during the bind procedure because this is done asynchronously by the Android system. So after binding the Service you can’t just directly call remote methods but you have to wait for a callback which notifies your client that the connection was established.
Defining the Remote Interface
The Service in the example has a method which will generate a String containing the current time. To make this method accessible to remote clients we have to declare this functionality within an AIDL file. The first thing we need to declare in this file is the package where the interface is located. In our example this will be com.appsolut.example.aidlMessageService. This statement is followed by the interface tag which will declare the name of the interface (IRemoteMessageService). Inside the interface block we can define methods. In our example we provide a method which will return a String String getMessage();. The content of this file must be saved in an .aidl file which is named like the interface is called. Furthermore the file must be located within the package folder.
01 | /* The package where the aidl file is located */ |
02 | package com.appsolut.example.aidlMessageService; |
04 | /* The name of the remote service */ |
05 | interface IRemoteMessageService { |
07 | /* A simple Method which will return a message string */ |
The compiler will now automatically generate the interface IRemoteMessageService which contains an abstract class called Stub. The generated file will be placed within the gen folder of your project.
Implementing the Remote Interface
The functionality of the Remote Interface is now described by the interface but we still have to implement this interface. In our example this is done in the TimeMessageService class. Generally the implementations of such a remote interface have to extend the abstract Stub class within the interface. The Stub class contains an abstract method which is called like the method which we declared in the AIDL interface. Within this method we can implement our functionality which will be executed by a remote client. Furthermore, our class contains a field variable containing a reference to the service because we will access a method in the service from our remote interface.
01 | public class TimeMessageService extends IRemoteMessageService.Stub { |
03 | private final AIDLMessageService service; |
05 | public TimeMessageService(AIDLMessageService service) { |
06 | this .service = service; |
10 | public String getMessage() throws RemoteException { |
11 | return service.getStringForRemoteService(); |
Implementing the Service
The only RPC specific part within the Service class is done in the onBind method. Within this method we have to decide on the bind intent which binder we will return. So we only check if the intent action is the expected action for the message service and then return our implementation of the remote interface.
01 | public class AIDLMessageService extends Service { |
02 | private static final String APPSOLUT_INTENT_ACTION_BIND_MESSAGE_SERVICE = "appsolut.intent.action.bindMessageService" ; |
05 | public IBinder onBind(Intent intent) { |
06 | if (APPSOLUT_INTENT_ACTION_BIND_MESSAGE_SERVICE.equals(intent.getAction())) { |
07 | return new TimeMessageService( this ); |
12 | String getStringForRemoteService() { |
13 | return getString(R.string.time_message) + ( new SimpleDateFormat( " hh:mm:ss" ).format(System.currentTimeMillis())); |
Implementing the Client
Before a client can use the remote interface it has to know about the descirption of that interface. This is done by copying the .aidl interface description into the client project. This way you won’t have to publish any code if third-party applications want to extend your own app.
As seen in the “The basic concept of RPCs in Android” chapter the bindService which will be used within the client requires a ServiceConnection. Therefore, before we implement the client Activity we will implement our ServiceConnection class.
Implementing the Service Connection
You can find the ServiceConnection which we use in the example in the RemoteMessageServiceServiceConnection class. This class contains a field variable where the remote interface and a reference to the Activity (called DisplayRemoteMessage) will be stored:
1 | public class RemoteMessageServiceServiceConnection implements ServiceConnection { |
2 | private final DisplayRemoteMessage parent; |
3 | private IRemoteMessageService service; |
5 | public RemoteMessageServiceServiceConnection( |
6 | DisplayRemoteMessage parent) { |
There are many different approaches how to implement the access to the remote interface within the ServiceConnection class. In this example I’ve decided to encapsulate the method call within the ServiceConnection class which will hide the asynchronous connection establishment of the bind call. The Activity itself will use the ServiceConnection to trigger the getMessage method in the remote service and provides a callback method named theMessageWasReceivedAsynchronously(String message) which will be called from the ServiceConnection after the requested data is available.
Core Methods
The core methods in a ServiceConnection class are defined by two abstract methods which we need to implement.
The onServiceConnected Method
The first is the onServiceConnected method. For our ServiceConnection there are two tasks which we have to handle within this method. First of all is the retrieval of the remote interface. This is done by the .Stub.asInterface method which will cast the IBinder object to the remote interface. Additionally, this method call will add a proxy if needed – see my note at the end of this post about this. The remote interface will be stored in the field variable service.
2 | public void onServiceConnected(ComponentName name, IBinder service) { |
3 | this .service = IRemoteMessageService.Stub.asInterface(service); |
After that we need to query the message from the remote service once the connection was established. This is required because the implementation of our ServiceConnection hides the asynchronous connection establishment process. Because the connection was established the Activity requested the data already once so we need to query the data from the remote service by calling the service.getMessage() method. The message will then be passed to the Activity with the help of the theMessageWasReceivedAsynchronously method.
5 | parent.theMessageWasReceivedAsynchronously( this .service.getMessage()); |
6 | } catch (RemoteException e) { |
The onServiceDisconnected Method
The second core method is the onServiceDisconnected. When this callback is called something went wrong with the connection so we need to remove the remote interface from the field variable.
2 | public void onServiceDisconnected(ComponentName name) { |
Additional Methods
Additionally to these two methods, I’ve added three methods which are used to encapsulate the asynchronous connection establishment and to avoid errors when working with the remote interface.
The safelyConnectTheService Method
This method encapsulates the bindService process for the Activity. It will avoid multiple bindService calls by checking if the connection is currently established. After that an Intent is generated with the appropriate action for the IRemoteMessageService remote interface. Furthermore the package and class name for the receiving service are set to our Service. This intent will be used in the bindService call which is executed on the Activity. The ServiceConnection parameter of this call is our own ServiceConnection – this. And because we want the Service to be created if it is currently not running we set the flag to Context.BIND_AUTO_CREATE.
01 | private static final String AIDL_MESSAGE_SERVICE_CLASS = ".AIDLMessageService" ; |
02 | private static final String AIDL_MESSAGE_SERVICE_PACKAGE = "com.appsolut.example.aidlMessageService" ; |
03 | private static final String APPSOLUT_INTENT_ACTION_BIND_MESSAGE_SERVICE = "appsolut.intent.action.bindMessageService" ; |
04 | public void safelyConnectTheService() { |
06 | Intent bindIntent = new Intent(APPSOLUT_INTENT_ACTION_BIND_MESSAGE_SERVICE); |
07 | bindIntent.setClassName(AIDL_MESSAGE_SERVICE_PACKAGE, AIDL_MESSAGE_SERVICE_PACKAGE + AIDL_MESSAGE_SERVICE_CLASS); |
08 | parent.bindService(bindIntent, this , Context.BIND_AUTO_CREATE); |
The safelyDisconnectTheService Method
Because the onServiceDisconnected will only be called when something unexpected closed the connection I’ve added this method to the ServiceConnection. This method handles the unbinding for the Activity. First it checks whether a connection is currently established by checking if the remote interface is not null. Then it will remove the reference for the remote interface which will indicate that the connection is closed. Finally the unbindService method in the Activity can be called which will disconnect the Activity from the remote service.
1 | public void safelyDisconnectTheService() { |
4 | parent.unbindService( this ); |
The safelyQueryMessage Method
This method finally fully encapsulates the asynchronous connection establishment. If there is currently no connection available (service == null) the safelyConnectTheService method is called which will bind the remote service (after the connection was established successfully the getMessage call will be executed in the onServiceConnected method as shown above). Otherwise the getMessage call can be executed directly because the remote service is connected. The content of the message will then be passed directly to the activity with the help of the callback method.
01 | public void safelyQueryMessage() { |
03 | safelyConnectTheService(); |
06 | parent.theMessageWasReceivedAsynchronously(service.getMessage()); |
07 | } catch (RemoteException e) { |
Implementing the Activity
Because our RemoteMessageServiceServiceConnection class handles all aspects of the connection the activity is reduced to bare GUI code. Our GUI contains two buttons: one to update the message and another one to disconnect the remote service. The disconnect button is used to demonstrate that our ServiceConnection handles everything for us. During the onCreate of the Activity listeners are added to the buttons which will call the safelyQueryMessage or safelyDisconnectTheService method.
01 | public class DisplayRemoteMessage extends Activity { |
02 | private Button disconnectButton; |
03 | private Button queryButton; |
04 | private TextView messageTextView; |
05 | private RemoteMessageServiceServiceConnection remoteServiceConnection; |
07 | public void onCreate(Bundle savedInstanceState) { |
08 | super .onCreate(savedInstanceState); |
09 | setContentView(R.layout.main); |
10 | remoteServiceConnection = new RemoteMessageServiceServiceConnection( this ); |
11 | disconnectButton = (Button)findViewById(R.id.disconnectButton); |
12 | queryButton = (Button)findViewById(R.id.queryButton); |
13 | messageTextView = (TextView)findViewById(R.id.messageTextView); |
15 | disconnectButton.setOnClickListener( new OnClickListener() { |
17 | public void onClick(View v) { |
18 | remoteServiceConnection.safelyDisconnectTheService(); |
22 | queryButton.setOnClickListener( new OnClickListener() { |
25 | public void onClick(View v) { |
26 | remoteServiceConnection.safelyQueryMessage(); |
To finish the Activity we need to add the callback method which is used by the ServiceConnection to notify the Activity that the message was received from the remote service:
30 | void theMessageWasReceivedAsynchronously(String message) { |
31 | messageTextView.setText(message); |
Summary and Lookout
The Android RPC mechanism is a powerful tool which can be used to realize inter process communication (IPC) in Android. In larger Apps the overhead which is required to define the remote interface and the service connection on the client-side will be much smaller than a permanent communication with intents and the code will be less error-prone. For more information on using the Android RPC / IPC and AIDL you can look at the following links:
NOTE: When your Service and the Service Consumer (e.g. an Activity) runs in the same process you should not use the asInterface method to retrieve the remote interface from the binder. In this case you can use the local binder pattern to improve the performance. Please refer to the following links: