Remote Service Tutorial

Tutorials with advanced 'difficulty' and more Lines of Code.

Remote Service Tutorial

Postby saigeethamn » Thu Sep 24, 2009 8:05 am

Services typically are required to run for a long time and hence should run in their own thread. Such services can be invoked by any number of clients who want to connect to the service, invoke a few methods on the service and finally release the service, probably to serve more clients or close down.

Here, I would like to introduce you to the concept of connecting to a remote service and the kind of support provided by the android platform for the same.

You can see this to understand how Local services can be created and used. The difference between the two mainly is that the local service runs in the same process as the application that started it and hence the life of the local service is dependent on the life of the said application while remote service can run in its own process. This causes a challenge of inter-process communication. If one process wants to communicate with another process, the object that is passed between the two needs to be marshaled.

For this purpose, Android provides the AIDL (Android Interface Definition Language) tool that handles the marshaling as well as the communication.

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:
Code: Select all
package com.collabera.labs.sai;

interface IMyRemoteService {

   int getCounter();
}


Once you write this AIDL file (.aidl) in eclipse, it will automatically generate the Remote interface corresponding to this 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:

Code: Select all
private IMyRemoteService.Stub myRemoteServiceStub = new IMyRemoteService.Stub() {
      public int getCounter() throws RemoteException {
         return counter;
      }
   };
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 incrementing a counter in a separate thread. This thread is created in the onStart() method as this gets certainly called whether the service is connected to by a call to startService(intent) or by bindService(…). Please read the lifecycle of a service if this needs more clarity. Here are the over-ridden onCreate(), onStart() and onDestroy() methods. Note that the resources are all released in the onDestroy() method.

Code: Select all
   public void onCreate() {
      super.onCreate();
      Log.d(getClass().getSimpleName(),"onCreate()");
   }
   public void onStart(Intent intent, int startId) {
      super.onStart(intent, startId);
      serviceHandler = new Handler();
      serviceHandler.postDelayed(myTask, 1000L);
      Log.d(getClass().getSimpleName(), "onStart()");
   }
   public void onDestroy() {
      super.onDestroy();
      serviceHandler.removeCallbacks(myTask);
      serviceHandler = null;
      Log.d(getClass().getSimpleName(),"onDestroy()");
   }


A little explanation: In the onStart() method, I created a new Handler object that will spawn out a new task that implements the Runnable interface. This thread does the job of incrementing the counter. Here is the code for the Task class – an inner class of the RemoteService class.

Code: Select all
class Task implements Runnable {
   public void run() {
      ++counter;
      serviceHandler.postDelayed(this,1000L);
      Log.i(getClass().getSimpleName(), "Incrementing counter in the run method");
   }
}


An object of this Task class is passed to the serviceHandler object as a message that needs to be executed after 1 second. The Task class implements the run() method in which we repeatedly post the same message to the serviceHandler. Thus, this becomes a repeated task till all the messages in the serviceHandler queue are deleted by calling the removeCallbacks() method on the serviceHandler in the destroy() method of the RemoteService class.

Note that the onDestroy() method thus stops this thread and set the serviceHandler to null. This completes the implementation of the RemoteService class. The complete code is downloadable here.
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.

Code: Select all
private void startService(){
     if (started) {
       Toast.makeText(RemoteServiceClient.this, "Service already started", Toast.LENGTH_SHORT).show();
     } else {
       Intent i = new Intent();
       i.setClassName("com.collabera.labs.sai", "com.collabera.labs.sai.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.

Code: Select all
private void bindService() {
     if(conn == null) {
        conn = new RemoteServiceConnection();
        Intent i = new Intent();
        i.setClassName("com.collabera.labs.sai", "com.collabera.labs.sai.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:

Code: Select all
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 getCounter(). In this example, the invocation is done by clicking the invoke button. That would update the counter text that is below the button. Let us see the invoke method:

Code: Select all
private void invokeService() {
     if(conn == null) {
        Toast.makeText(RemoteServiceClient.this, "Cannot invoke - service not bound", Toast.LENGTH_SHORT).show();
     } else {
        try {
           int counter = remoteService.getCounter();
           TextView t = (TextView)findViewById(R.id.notApplicable);
           t.setText( "Counter value: "+Integer.toString( counter ) );
           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):
Code: Select all
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.

Code: Select all
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.collabera.labs.sai", "com.collabera.labs.sai.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!

For many more tutorials on basics like explicit intent, implicit intent, activity getting a result back from another activity, local service, notification etc, you may visit this URL for basic android tutorials. You will have examples with downloadable code.
Attachments
RemoteService.zip
Complete source code for the remote service
(47 KiB) Downloaded 1996 times
A person who loves to share my ten cents of knowledge
http://saigeethamn.blogspot.com
saigeethamn
Junior Developer
Junior Developer
 
Posts: 15
Joined: Wed Aug 26, 2009 10:51 am

Top

Postby aibo99 » Fri Oct 09, 2009 8:03 am

Thank you for your tutorial.

I have some questions as below

1. Is there only one instance of RemoteService running ?

2. If the user quits the RemoteServiceClient, will the RemoteService thread still alive ?

Thanks
aibo99
Freshman
Freshman
 
Posts: 9
Joined: Mon Oct 05, 2009 7:18 am

Postby saigeethamn » Fri Oct 09, 2009 8:09 am

aibo99 wrote:1. Is there only one instance of RemoteService running ?



Yes, there is only one instance running. Any number of clients can connect to it by binding to it and get the services exposed through the remote interface / API.

aibo99 wrote:2. If the user quits the RemoteServiceClient, will the RemoteService thread still alive ?



It depends. As soon as the last client to the remote service has unbound, the service can be stopped by the OS automatically. Depending on various factors like memory availability or CPU overload, the service can be stopped.
A person who loves to share my ten cents of knowledge
http://saigeethamn.blogspot.com
saigeethamn
Junior Developer
Junior Developer
 
Posts: 15
Joined: Wed Aug 26, 2009 10:51 am

Postby aibo99 » Fri Oct 09, 2009 8:25 am

Thank you for your answer.

The following scenario makes me confused. Please help.

1. User starts the RemoteServiceClient app, then clicks 'Start' button on UI to start the RemoteService thread remotely.

2. He just pushes 'Home' button on handset to leave the RemoteServiceClient app.

3. After several minutes, he starts the RemoteServiceClient app again, and clicks 'Start' button on UI to start the RemoteService.

My questions :

a) If the first instance of RemoteService still alive, will it create another instance of RemoteService in step 3 ? (how to avoid multiple instances created ?)

b) What is the 'started' flag in RemoteServiceClient after step 2 ? (returns to false?)

c) What is the consequence when he leaves the RemoteServiceClient without pressing the 'Unbind' button ?

Thanks again.
aibo99
Freshman
Freshman
 
Posts: 9
Joined: Mon Oct 05, 2009 7:18 am

Postby zoniq » Fri Oct 09, 2009 9:41 am

Thanks for the tutorial. I also had a look at your Local Service tutorial, but I'm wondering how I communicate from my Activity to the Local Service and exchange objects between the two. Any hints?

I posted a more detailed question here.

Thanks
zoniq
Junior Developer
Junior Developer
 
Posts: 21
Joined: Mon Sep 28, 2009 12:33 pm

Postby saigeethamn » Fri Oct 09, 2009 11:51 am

aibo99 wrote:1. User starts the RemoteServiceClient app, then clicks 'Start' button on UI to start the RemoteService thread remotely.

2. He just pushes 'Home' button on handset to leave the RemoteServiceClient app.

3. After several minutes, he starts the RemoteServiceClient app again, and clicks 'Start' button on UI to start the RemoteService.

My questions :

a) If the first instance of RemoteService still alive, will it create another instance of RemoteService in step 3 ? (how to avoid multiple instances created ?)


yes, the first remote service is still very much alive. if you come back and restart the client and click on the start button again, you will get a message that the remote service has already been started. It will not start another service unless the first one has been killed by the platform due to memory reasons. In case that has been killed, the start button will start a new service. You can make out the difference by seeing the counter. If it has been running for all the time the client moved away, when you come back and bind and invoke, it will give a large count.
The basic thing is that the remote service client and the remote service are not in the same process. The remote service is a separate process and will not get killed just by moving away from the client activity.
Also note that whether the client is bound or not, the service is running and this is visible by the counter being continuously incremented. You can bind, unbind, bind , unbind any number of times without stopping the service and you will notice that the counter has been incrementing all along, even when the client was closed by moving to some other activity like home screen.

aibo99 wrote:b) What is the 'started' flag in RemoteServiceClient after step 2 ? (returns to false?)

The started flag is to indicate that the service has been started and running. This does not return false for me after step 2. After step 2, if i come back and click on start, I get a message "service" already started. Please check if the flag is set to true in the first place after step 1.

aibo99 wrote:c) What is the consequence when he leaves the RemoteServiceClient without pressing the 'Unbind' button ?

In this case the client is still bound to the remote service. Only the client is not in the foreground and hence nor visible. If you were to restart the client, actually it will not restart but bring the background running client to the foreground and you can continue invoke the method. However, this behaviour is not gauranteed because an activity which is not in the foreground could anytime be killed. If that is the case, then you have to bind to the service once again before invoking the method.
A person who loves to share my ten cents of knowledge
http://saigeethamn.blogspot.com
saigeethamn
Junior Developer
Junior Developer
 
Posts: 15
Joined: Wed Aug 26, 2009 10:51 am

Top

Postby saigeethamn » Fri Oct 09, 2009 12:03 pm

zoniq wrote:Thanks for the tutorial. I also had a look at your Local Service tutorial, but I'm wondering how I communicate from my Activity to the Local Service and exchange objects between the two. Any hints?

I posted a more detailed question here.

Thanks


I think you have to pass data to the local service in the standard way through an intent. When you are starting the local service you pass an intent, right? into the same intent, put your data through intent.putExtra(String, Bundle) and retrieve it through the same intent in the local service.
A person who loves to share my ten cents of knowledge
http://saigeethamn.blogspot.com
saigeethamn
Junior Developer
Junior Developer
 
Posts: 15
Joined: Wed Aug 26, 2009 10:51 am

Postby zoniq » Fri Oct 09, 2009 12:13 pm

saigeethamn wrote:I think you have to pass data to the local service in the standard way through an intent. When you are starting the local service you pass an intent, right? into the same intent, put your data through intent.putExtra(String, Bundle) and retrieve it through the same intent in the local service.


But wouldn't that be only once, namely when the service is starting the first time?

What I want to do is to put a HttpClient in a new thread in the service, which always runs and just waits for requests from different activities and responds back to these activities.

Also if I would do it via an intent how do it send the response back to the activity?
zoniq
Junior Developer
Junior Developer
 
Posts: 21
Joined: Mon Sep 28, 2009 12:33 pm

Postby saigeethamn » Fri Oct 09, 2009 1:00 pm

zoniq wrote:
But wouldn't that be only once, namely when the service is starting the first time?

What I want to do is to put a HttpClient in a new thread in the service, which always runs and just waits for requests from different activities and responds back to these activities.

Also if I would do it via an intent how do it send the response back to the activity?


You need to do many steps for this. First you will have to implement the bind and unbind methods on the local service. You will also have to implement any application specific methods.
Once one activity has started the local service, if you want to connect to the same local service from another activity, you will have to create a "connection" to the already running local service, and then invoke your application specific methods.
Please see the Local Service Binding and Local Service Controller examples in the API Demos that come with the emulator.
A person who loves to share my ten cents of knowledge
http://saigeethamn.blogspot.com
saigeethamn
Junior Developer
Junior Developer
 
Posts: 15
Joined: Wed Aug 26, 2009 10:51 am

Postby aibo99 » Fri Oct 09, 2009 5:19 pm

saigeethamn, thank you very much for your explanation and contribution.
aibo99
Freshman
Freshman
 
Posts: 9
Joined: Mon Oct 05, 2009 7:18 am

Postby zoniq » Wed Oct 14, 2009 5:07 pm

aibo99 wrote:saigeethamn, thank you very much for your explanation and contribution.

Same here. Thanks a lot for your explanations and hints. They helped me a lot. Will publish my results soon as a code sample, because I'm sure some other people are running into the same difficulties.
zoniq
Junior Developer
Junior Developer
 
Posts: 21
Joined: Mon Sep 28, 2009 12:33 pm

Postby niftysting » Thu Oct 29, 2009 5:00 am

I m facing a similar issue. For the same remote service as in this tutorial, i have written a client in another application which is present in a different package. The problem is, the invoke method in the client does not identify the interface definition because it is in another package. So when i say invoke from the new client it raises a "java.lang.SecurityException: Binder invocation to an incorrect interface" very understandably. I tried to import the package, all in vain. How to share the interface definition across packages??

Thanks in advance,
Nothing can come of nothing
niftysting
Freshman
Freshman
 
Posts: 5
Joined: Tue Oct 27, 2009 5:36 am

Postby Pr0v0dn1k » Thu Nov 19, 2009 10:13 am

saigeethamn wrote:As soon as the last client to the remote service has unbound, the service can be stopped by the OS automatically. Depending on various factors like memory availability or CPU overload, the service can be stopped.

You may use setForeground(true) in service onCreate() to prevent stopping the service on low memory. The service will be treated in the same priority as any foreground activity.
But, this works up to SDK 1.6. In 2.0 it is depricated. The new method startForeground(int, Notification) is proposed instead.
Pr0v0dn1k
Freshman
Freshman
 
Posts: 6
Joined: Sun Nov 08, 2009 4:11 pm

Postby Noob » Wed Dec 09, 2009 8:11 am

Dear all,

I created a service and a few threads separately in different .java files.

In the service, i start all the threads that i created. Within each thread, there is a loop that will allow it to continue running and perform a tasks until i end the service.

When I am actively using the phone for whatever purposes, the threads work properly and the expected outcome is achieved. However, if i leave the phone aside for a short period of time of around 10 min, the threads stop working.
Surprisingly, if i connect my handphone to my laptop and have eclipse run to show the LogCat, the threads function normally even though the screen on my handphones shows that the phone is sleeping.

I understand that service allows infinite usage even in the background. May I know if it also work for the situation mentioned above? If not, is there anyway to achieve my goals? Did it fail to work because the service i created is a local service?

Also, can I know whats the purpose of creating a local service if its life cycle is tied to its invoking application? Why dont we use a thread instead? How are they different?

Thanks in advance.

Best Regards
Sad Noob
Noob
Freshman
Freshman
 
Posts: 5
Joined: Mon Oct 26, 2009 3:56 am

Postby wonghkc » Wed Mar 03, 2010 9:21 am

Thank you for your sharing. I've read many books and forum thread talking about service in Android. I found this is the best one, especially the relationship between AIDL, service class as well as the client class.

:)
wonghkc
Freshman
Freshman
 
Posts: 2
Joined: Mon Sep 14, 2009 2:00 am

Top
Next

Return to Advanced Tutorials

Who is online

Users browsing this forum: No registered users and 4 guests