Android Java basic knowledge ---AIDL3

Using the Android Interface Definition Language (AIDL) to make a Remote Procedure Call (RPC) in Android

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).

 

 

Overview of the Communication between an Activity and a Service using AIDL IPC / RPC

 

 

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:

1public boolean bindService (Intent service, ServiceConnection conn, intflags)

To bind a remote service we must define these three parameters:

  • Intent service – this parameter is the intent which will be used to locate the service
  • ServiceConnection conn – the service connection manages the connection to the remote interface. The ServiceConnection class contains callbacks for an established connection and an unexpectedly closed connection:
    • public void onServiceConnected (ComponentName name, IBinder service)
    • public void onServiceDisconnected (ComponentName name)

    It is important to understand that the onServiceDisconnected method will only be called if the connection was closed unexpectedly and not if the connection was closed by the client.

  • int flags – this parameter defines options which will be used during the bind process there are four possible parameters which can be combined (XOR):
    • 0 – no options
    • BIND_AUTO_CREATE – this flag will automatically create the service if it is not yet running
    • BIND_DEBUG_UNBIND – this flag will result in additional debug output when errors occur during the unbinding of the service. This flag should only be used during debugging
    • BIND_NOT_FOREGROUND – this flag will limit the service process priority so that the service won’t run at the foreground process priority.

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 */
02package com.appsolut.example.aidlMessageService;
03 
04/* The name of the remote service */
05interface IRemoteMessageService {
06 
07    /* A simple Method which will return a message string */
08    String getMessage();
09 
10}

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.

01public class TimeMessageService extends IRemoteMessageService.Stub {
02 
03    private final AIDLMessageService service;
04 
05    public TimeMessageService(AIDLMessageService service) {
06        this.service = service;
07    }
08 
09    @Override
10    public String getMessage() throws RemoteException {
11        return service.getStringForRemoteService();
12    }
13 
14}

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.

01public class AIDLMessageService extends Service {
02    private static final String APPSOLUT_INTENT_ACTION_BIND_MESSAGE_SERVICE = "appsolut.intent.action.bindMessageService";
03 
04    @Override
05    public IBinder onBind(Intent intent) {
06        if(APPSOLUT_INTENT_ACTION_BIND_MESSAGE_SERVICE.equals(intent.getAction())) {
07            return new TimeMessageService(this);
08        }
09        return null;    // no matching remote interface available
10    }
11 
12    String getStringForRemoteService() {
13        return getString(R.string.time_message) + (new SimpleDateFormat(" hh:mm:ss").format(System.currentTimeMillis()));
14    }
15}

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:

1public class RemoteMessageServiceServiceConnection implementsServiceConnection {
2    private final DisplayRemoteMessage parent;
3    private IRemoteMessageService service;
4 
5    public RemoteMessageServiceServiceConnection(
6            DisplayRemoteMessage parent) {
7        this.parent = parent;
8    }

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.

1@Override
2public 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.

4try {
5parent.theMessageWasReceivedAsynchronously(this.service.getMessage());
6    catch (RemoteException e) {
7        
8    }
9}
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.

1@Override
2public void onServiceDisconnected(ComponentName name) {
3    service = null;
4}
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.

01private static final String AIDL_MESSAGE_SERVICE_CLASS =".AIDLMessageService";
02private static final String AIDL_MESSAGE_SERVICE_PACKAGE ="com.appsolut.example.aidlMessageService";
03private static final String APPSOLUT_INTENT_ACTION_BIND_MESSAGE_SERVICE ="appsolut.intent.action.bindMessageService";
04public void safelyConnectTheService() {
05    if(service == null) {
06        Intent bindIntent = newIntent(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);
09    }
10}
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.

1public void safelyDisconnectTheService() {
2    if(service != null) {
3        service = null;
4        parent.unbindService(this);
5    }
6}
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.

01public void safelyQueryMessage() {
02    if(service == null) {
03        safelyConnectTheService();
04    else {
05        try {
06            parent.theMessageWasReceivedAsynchronously(service.getMessage());
07        catch (RemoteException e) {
08            
09        }
10    }
11}

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.

01public class DisplayRemoteMessage extends Activity {
02    private Button disconnectButton;
03    private Button queryButton;
04    private TextView messageTextView;
05    private RemoteMessageServiceServiceConnection remoteServiceConnection;
06    @Override
07    public void onCreate(Bundle savedInstanceState) {
08        super.onCreate(savedInstanceState);
09        setContentView(R.layout.main);
10        remoteServiceConnection = newRemoteMessageServiceServiceConnection(this);
11        disconnectButton = (Button)findViewById(R.id.disconnectButton);
12        queryButton = (Button)findViewById(R.id.queryButton);
13        messageTextView = (TextView)findViewById(R.id.messageTextView);
14 
15        disconnectButton.setOnClickListener(new OnClickListener() {
16            @Override
17            public void onClick(View v) {
18                remoteServiceConnection.safelyDisconnectTheService();
19            }
20        });
21 
22        queryButton.setOnClickListener(new OnClickListener() {
23 
24            @Override
25            public void onClick(View v) {
26                remoteServiceConnection.safelyQueryMessage();
27            }
28        });
29    }

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:

30void theMessageWasReceivedAsynchronously(String message) {
31        messageTextView.setText(message);
32    }
33}

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:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值