Search This Blog

Tuesday, June 26, 2012

Role of AIDL

In this post I am going to share the use of AIDL ( Android Interface Definition Language) with an example
AIDL can be implemented using parcels and also by normal java interfaces. So the below example will be a combination of both these ways, Please forgive me for my bad English.....
The main need of AIDL is to achieve RPC in android

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 client.
In order to bind to a remote interface we need to define the following 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. So while disconnecting from service it is must to nullify service instance
     
  • int flags – this parameter defines options which will be used during the bind process there are four possible parameters which can be combined (OR):
    • 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. This interface provides all methods of the remote service 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.

    Fundamentals of Parcels and Parcelables

     In Android, Parcels are used to transmit messages. Unlike to the java serialization, the Parcels are implanted as high-performance containers for the Android inter process communication. By implementing the Parcelable interface you declare that it’s possible to transform (marshall) your class into a Parcel and back (demarshall). Because the Parcels are designed for performance you should always use Parcelables instead of using the java serialization (which would also be possible) when doing IPC in android. Even when you are communicating with Intents you can still use Parcels to pass data within the intent instead of serialized data which is most likely not as efficient as Parcels.

    Create Parcelable Interface

    Let us jump into some bit of code... Create a file named MyParcelableMessage.aidl and do the following code in it..
    /* The package where the aidl file is located */
    package com.test.aidlparcel;

    /* Declare our message as a class which implements the Parcelable interface */
    parcelable MyParcelableMessage;
    After defining .aidl file we define corresponding java class that is need to parceled to activity. So we need to implement our java class using Parcelable Interface, The android.os.Parcelable interface defines two methods which have to be implemented:
    int describeContents()   
    This method can be used to give additional hints on how to process the received parcel. I am not much aware of need of this method So just implementing it as follows
    /**
         * Method which will give additional hints how to process
         * the parcel. For example there could be multiple
         * implementations of an Interface which extends the Parcelable
         * Interface. When such a parcel is received you can use
         * this to determine which object you need to instantiate.
         */
        public int describeContents() {
            return 0;            // nothing special about our content
        }
     void writeToParcel(Parcel dest, int flags)

    This is the core method which is called when this object is to be marshalled to a parcel object. In this method all required data fields should be added to the “dest” Parcel so that it’s possible to restore the state of the object within the receiver during the demarshalling.
    /**
         * Method which will be called when this object should be
         * marshalled to a Parcelable object.
         * Add all required data fields to the parcel in this
         * method.
         */
        public void writeToParcel(Parcel outParcel, int flags) {
            outParcel.writeString(message);
            outParcel.writeInt(textSize);
            outParcel.writeInt(textColor);
            outParcel.writeInt(textTypeface.getStyle());
        }
     
    Furthermore it is necessary to provide a static CREATOR field in any implementation of the Parcelable interface. The type of this CREATOR must be of Parcelable.Creator<T>. This CREATOR will act as a factory to create objects during the demarshalling of the parcel. This interface defines two methods and T specifies object which is need to be parceled.
    /**
         * Factory for creating instances of the Parcelable class.
         */
        public static final Parcelable.Creator<MyParcelableMessage> CREATOR = new Parcelable.Creator<MyParcelableMessage>() {
           
            /**
             * This method will be called to instantiate a MyParcelableMessage
             * when a Parcel is received.
             * All data fields which where written during the writeToParcel
             * method should be read in the correct sequence during this method.
             */
            @Override
            public MyParcelableMessage createFromParcel(Parcel in) {
                String message = in.readString();
                int fontSize = in.readInt();
                int textColor = in.readInt();
                Typeface typeface = Typeface.defaultFromStyle(in.readInt());
                return new MyParcelableMessage(message, fontSize, textColor, typeface);
            }

            /**
             * Creates an array of our Parcelable object.
             */
            @Override
            public MyParcelableMessage[] newArray(int size) {
                return new MyParcelableMessage[size];
            }
        };

    Implementing Remote Interface

    As I mentioned above these example does RPC using Interfacing of Java objects and here I am going to define an another .adil file which transfers Parcelable object through java interface

    /* Import our Parcelable message */
    import com.test.aidlparcel.MyParcelableMessage;

    /* The name of the remote service */
    interface IRemoteParcelableMessageService {

        /* A simple Method which will return a message
         * The message object implements the Parcelable interface
         */
        MyParcelableMessage getMessage();

    }
    After creating this aidl file it will generate a java file in gen directory on your project folder. So we will use of the stub to add details to be parceled to main activity
    Create Java file and do the following
    import android.graphics.Typeface;
    import android.os.RemoteException;

    public class TimeParcelableMessageService extends IRemoteParcelableMessageService.Stub {
        private final static int MAX_FONT_SIZE_INCREASE = 40;
        private final static int MIN_FONT_SIZE = 10;
       
        private final AIDLParcelableMessageService service;

        public TimeParcelableMessageService(AIDLParcelableMessageService service) {
            this.service = service;
        }
       
        @Override
        public MyParcelableMessage getMessage() throws RemoteException {
            String message = service.getStringForRemoteService();
            int fontSize = (int)(Math.random()*MAX_FONT_SIZE_INCREASE) + MIN_FONT_SIZE;
            int textColor = (int)(Math.random()*Integer.MAX_VALUE);
          
            int randomTextStyleSelector = (int)(Math.random()*3);
            int textStyle;
            switch (randomTextStyleSelector) {
            case 0:
                textStyle = Typeface.BOLD;
                break;
            case 1:
                textStyle = Typeface.BOLD_ITALIC;
                break;
            case 2:
                textStyle = Typeface.ITALIC;
                break;
            default:
                textStyle = Typeface.NORMAL;
                break;
            }
          
            return new MyParcelableMessage(message, fontSize, textColor, Typeface.defaultFromStyle(textStyle));
        }

    }
    The above class will provide informations like Text to be displayed with its font size color and text style. In this class we get the message to be provided to parcelable class is obtained from another class that invokes during bind process.

    Implementing the Service

     Create a Java class as shown below
    import java.text.SimpleDateFormat;


    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.util.Log;

    public class AIDLParcelableMessageService extends Service {
        private static final String AIDL_INTENT_ACTION_BIND_MESSAGE_SERVICE = "aidl.intent.action.bindParcelableMessageService";
        private final static String LOG_TAG = AIDLParcelableMessageService.class.getCanonicalName();

        @Override
        public void onCreate() {
            super.onCreate();
            Log.d(LOG_TAG,"The AIDLParcelableMessageService was created.");
        }

        @Override
        public void onDestroy() {
            Log.d(LOG_TAG,"The AIDLParcelableMessageService was destroyed.");
            super.onDestroy();
        }


        @Override
        public IBinder onBind(Intent intent) {
            if(AIDL_INTENT_ACTION_BIND_MESSAGE_SERVICE.equals(intent.getAction())) {
                Log.d(LOG_TAG,"The AIDLParcelableMessageService was binded.");
                return new TimeParcelableMessageService(this);
            }
            return null;
        }

        String getStringForRemoteService() {
            return getString(R.string.time_message) + (new SimpleDateFormat(" hh:mm:ss").format(System.currentTimeMillis()));
        }

    }
    The above class handles service binding operation from remote client and provides the current system time to the parcleable interface.

    Implementing the Client

     Before Implementing client we define a remote class that implements ServiceConnection. The main objective of this class is that you won’t have to publish any code if third-party applications want to extend your own app.
    The following are the core methods of ServiceConnection Class

     The onServiceConnected Method

     The first is the onServiceConnected method. Retrieval of the remote interface is done by the .Stub.asInterface method which will cast the IBinder object to the remote interface.
    /* Called when a connection to the Service has been established,
         * with the IBinder of the communication channel to the Service. */
       
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(LOG_TAG, "The service is now connected!");
            // Retrive Remote Interface
            this.service = IRemoteParcelableMessageService.Stub.asInterface(service);
            Log.d(LOG_TAG, "Querying the message...");
            try {
                /*
                 * This call is required because the connect is an asynchronous call
                 * so the activity has to be notified that the connection is now
                 * established and that the message was queried.
                 */
                parent.theMessageWasReceivedAsynchronously(this.service.getMessage());
            } catch (RemoteException e) {
                Log.e(LOG_TAG, "An error occured during the call.");
            }
        }

    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.
    //Called when a connection to the Service has been lost.

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(LOG_TAG, "The connection to the service got disconnected unexpectedly!");
            service = null;
        }
    Now to connect and disconnect from the service we are not directly accessing the above defined methods and defining that with the following methods

    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.
    /**
         * Method to connect the Service.
         */
        public void safelyConnectTheService() {
            if(service == null) {
                Intent bindIntent = new Intent(AIDL_INTENT_ACTION_BIND_MESSAGE_SERVICE);
                bindIntent.setClassName(AIDL_MESSAGE_SERVICE_PACKAGE, AIDL_MESSAGE_SERVICE_PACKAGE + AIDL_MESSAGE_SERVICE_CLASS);
                parent.bindService(bindIntent, this, Context.BIND_AUTO_CREATE);
                Log.d(LOG_TAG, "The Service will be connected soon (asynchronus call)!");
            }
        }
    /**
         * Method to safely query the message from the remote service
         */
        public void safelyQueryMessage() {
            Log.d(LOG_TAG, "Trying to query the message from the Service.");
            if(service == null) {    // if the service is null the connection is not established.
                Log.d(LOG_TAG, "The service was not connected -> connecting.");
                safelyConnectTheService();
            } else {
                Log.d(LOG_TAG, "The Service is already connected -> querying the message.");
                try {
                    parent.theMessageWasReceivedAsynchronously(service.getMessage());
                } catch (RemoteException e) {
                    Log.e(LOG_TAG, "An error occured during the call.");
                }
            }

    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.
    /**
         * Method to disconnect the Service.
         * This method is required because the onServiceDisconnected
         * is only called when the connection got closed unexpectedly
         * and not if the user requests to disconnect the service.
         */
        public void safelyDisconnectTheService() {
            if(service != null) {
                service = null;
                parent.unbindService(this);
                Log.d(LOG_TAG, "The connection to the service was closed.!");
            }
        }

    Implementing Client

     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.
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.TextView;

    public class DisplayRemoteParcelableMessage extends Activity {
        private Button disconnectButton;
        private Button queryButton;
        private TextView messageTextView;
        private RemoteParcelableMessageServiceServiceConnection remoteServiceConnection;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            remoteServiceConnection = new RemoteParcelableMessageServiceServiceConnection(this);
            disconnectButton = (Button)findViewById(R.id.disconnectButton);
            queryButton = (Button)findViewById(R.id.queryButton);
            messageTextView = (TextView)findViewById(R.id.parcelableMessageTextView);
          
            disconnectButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    remoteServiceConnection.safelyDisconnectTheService();
                }
            });
           
            queryButton.setOnClickListener(new OnClickListener() {
               
                @Override
                public void onClick(View v) {
                    remoteServiceConnection.safelyQueryMessage();
                }
            });
        }
       
        void theMessageWasReceivedAsynchronously(MyParcelableMessage message) {
            message.applyMessageToTextView(messageTextView);
        }
    }

    Summary

    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.