Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Writing an Inter-Process Communication Service

Author: Rupesh Chavan
Published? true -- FormatLanguage: W

Problem:

How to write remote service in the android? How to access it from other application?

Solution:

Android has provided AIDL based programming interface that both the client and service agree upon in order to communicate with each other using interprocess communication(IPC).

Discussion:

Inter-process communication (IPC) is the key features of the Android programming model, to achieve the above goal it has provided following two mechanisms:

  1. Intent based communication
  2. Remote Service based communication

In this recipe we will be concentrating on Remote service based communication approach. This android feature allows you to make method calls that look "local" but are executed in another process. They involve use of Android's interface definition language(AIDL). The service has to declare a service interface in an aidl file and the AIDL tool will automatically create a java interface corresponding to the aidl file. The AIDL tool also generates a stub class that provides an abstract implementation of the service interface methods. The actual service class will have to extend this stub class to provide the real implementation of the methods exposed through the interface.

The service clients will have to invoke the onBind() method on the service to be able to connect to the service. The onBind() method returns an object of the stub class to the client. Here are the code related code snippets:

The AIDL file:

    package com.demoapp.service;

    interface IMyRemoteService {

      String getMessage();
    }

If you are using eclipse, it will automatically generate the Remote interface corresponding to your aidl file. The remote interface will also provide a stub inner class which has to have an implementation provided by the RemoteService class. The stub class implementation within the service class is as given here:

    private IMyRemoteService.Stub myRemoteServiceStub = new IMyRemoteService.Stub() {
        public int getMessage() throws RemoteException {
            return "Hello World!";
        }
    };
    // The onBind() method in the service class:
    public IBinder onBind(Intent arg0) {
        Log.d(getClass().getSimpleName(), "onBind()");
        return myRemoteServiceStub;
    }

Now, let us quickly look at the meat of the service class before we move on to how the client connects to this service class. My RemoteService class is just returning a string. Here are the over-ridden onCreate(), onStart() and onDestroy() methods. onCreate() method of service will be called only once in a service lifecycle. onStart() method will be called everytime service is started. Note that the resources are all released in the onDestroy() method.

    public void onCreate() {
        super.onCreate();
        Log.d(getClass().getSimpleName(),"onCreate()");
    }
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        Log.d(getClass().getSimpleName(), "onStart()");
    }
    public void onDestroy() {
       super.onDestroy();
       Log.d(getClass().getSimpleName(),"onDestroy()");
    }

Now coming to the client class - Here, for simplicity sake, I have put the start, stop, bind, release and invoke methods all in the same client. While in reality, one client may start and another can bind to the already started service.

There are 5 buttons one each for start, stop, bind, release and invoke actions. A client needs to bind to a service before it can invoke any method on the service. Here are the start and the bind methods.

    private void startService(){
        if (started) {
            Toast.makeText(RemoteServiceClient.this, "Service already started", Toast.LENGTH_SHORT).show();
        } else {
             Intent i = new Intent();
             i.setClassName("com.demoapp.service", "com.demoapp.service.RemoteService");
             startService(i);
             started = true;
             updateServiceStatus();
             Log.d( getClass().getSimpleName(), "startService()" );
        }                 
    }

An explicit intent is created and the service is started with the Context.startService(i) method. Rest of the code is to update some status on the UI. There is nothing specific to a remote service invocation here. It is on the bindService() method that we see the difference from a local service.

    private void bindService() {
        if(conn == null) {
            conn = new RemoteServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.demoapp.service", "com.demoapp.service.RemoteService");
            bindService(i, conn, Context.BIND_AUTO_CREATE);
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "bindService()" );
        } else {
              Toast.makeText(RemoteServiceClient.this, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

Here we get a connection to the remote service through the RemoteServiceConnection class which implements ServiceConnection Interface. The connection object is required by the bindService() method - an intent, connection object and the type of binding are to be specified. So, how do we create a connection to the RemoteService? Here is the implementation:

    class RemoteServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className,
      IBinder boundService ) {
            remoteService = IMyRemoteService.Stub.asInterface((IBinder)boundService);
            Log.d( getClass().getSimpleName(), "onServiceConnected()" );
        }

        public void onServiceDisconnected(ComponentName className) {
            remoteService = null;
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "onServiceDisconnected" );
        }
    };

The Context.BIND_AUTO_CREATE ensures that a service is created if one did not exist although the onstart() will be called only on explicit start of the service.

Once the client is bound to the service and the service has already started, we can invoke any of the methods that are exposed by the service. Here we have only one method and that is getMessage(). In this example, the invocation is done by clicking the invoke button. That would return the text message & update it below the button.

Let us see the invoke method:

    private void invokeService() {
        if(conn == null) {
            Toast.makeText(RemoteServiceClient.this, "Cannot invoke - service not bound", Toast.LENGTH_SHORT).show();
         } else {
               try {
                   String message = remoteService.getCounter();
                   TextView t = (TextView)findViewById(R.id.notApplicable);
                   t.setText( "Message: "+message );
                   Log.d( getClass().getSimpleName(), "invokeService()" );
               } catch (RemoteException re) {
                     Log.e( getClass().getSimpleName(), "RemoteException" );
               }
           }
    }

Once we use the service methods, we can release the service. This is done as follows (by clicking the release button):

    private void releaseService() {
        if(conn != null) {
            unbindService(conn);
            conn = null;
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "releaseService()" );
        } else {
              Toast.makeText(RemoteServiceClient.this, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
        }
    }

Finally we can stop the service by clicking the stop button. After this point no client can invoke this service.

    private void stopService() {
        if (!started) {
            Toast.makeText(RemoteServiceClient.this, "Service not yet started", Toast.LENGTH_SHORT).show();
        } else {
              Intent i = new Intent();
              i.setClassName("com.demoapp.service", "com.demoapp.service.RemoteService");
              stopService(i);
              started = false;
              updateServiceStatus();
              Log.d( getClass().getSimpleName(), "stopService()" );
        }
    }

These are the basics of working with a remote service on Android platform. All the best!

Note : If client & service are using different package structures then client has to include the .aidl file along with the package structure as in the service.

No records found.