Services / UI Thread

Background Tasks – Part 6

In the previous article we discussed the Android Service architecture and looked at how to properly implement your Service to both behave well and avoid being killed by the OS or a TaskKiller. In this concluding article in this series well look at IntentService and look at ways that we can trigger UI updates from a Service.

IntentService, as I mentioned in the previous article, is a useful way of performing a task and then shutting down. It is registered in the Manifest with one or more Intent Filters to declare which Intents should cause the Service to be started. We can then call startService() with an appropriate Intent as the single argument to cause the Service to be started. So let’s define an IntentService implementation:

[java] public class MyService extends IntentService
{
public static final String ACTION_DO_SOMETHING =
“com.stylingandroid.background.ACTION_DO_SOMETHING”;
public static final String ACTION_UPDATE =
“com.stylingandroid.background.ACTION_UPDATE”;
public static final String ACTION_PROGRESS =
“com.stylingandroid.background.ACTION_PROGRESS”;

public static final String EXTRA_UPDATE_TEXT = “UPDATE_TEXT”;
public static final String EXTRA_PROGRESS = “PROGRESS”;
public static final String EXTRA_MAX = “MAX”;

public MyService()
{
super( “MyService” );
}

@Override
protected void onHandleIntent( Intent intent )
{
if ( intent.getAction().equals( ACTION_DO_SOMETHING ) )
{
doSomething();
}
}

private void doSomething()
{
for ( int i = 0; i < 5; i++ ) { try { Thread.sleep( 2000 ); } catch ( InterruptedException e ) { } } } } [/java] First we are defining some string constants to represent the Actions that this Service supports, and also the names of some fields in the extras of the Intents. We must override the onHandleIntent() method and we’re matching the action of the Intent being received and calling an appropriate method.

Our doSomething() method is where the work is done, and we’re doing much the same as we have in previous articles in this series, but sending updates as broadcasts. The Intents that we’re sending via these broadcasts contains additional state in the form of extras.

Please note that we do not need to explicitly show down the Service by calling stopSelf() because this is done automatically once onHandleIntent() returns.

One word of warning: Performing long tasks in an IntentService does have the inherent risk that it will stop if the device goes to sleep. You can prevent this from happening by holding a WakeLock while onHandleIntent() is executing. Just make sure you release it before onHandleIntent() returns. Alternatively use Mark Murphy’s WakefulIntentService pattern.

Before we can use this Service we need to declare it in the Manifest:

[xml]


.
.
.






[/xml]

In the previous article I mentioned that I tend to see Services as tasks which need to execute independently of an Activity, but that does not necessarily mean that they only run without an Activity and there certainly be occasions when you will want to update an Activity if there is one. The easiest way to do this is to declare and register a BroadcastReceiver in your Activity and send broadcasts from your Service. Let’s add some broadcasts to the doSomething() method that we wrote earlier:

[java] private void doSomething()
{
for ( int i = 0; i < 5; i++ ) { Intent progressIntent = new Intent( ACTION_PROGRESS ); progressIntent.putExtra( EXTRA_PROGRESS, i ); progressIntent.putExtra( EXTRA_MAX, 4 ); sendBroadcast( progressIntent ); try { Thread.sleep( 2000 ); } catch ( InterruptedException e ) { } } Intent updateIntent = new Intent( ACTION_UPDATE ); updateIntent.putExtra( EXTRA_UPDATE_TEXT, "Finished Service" ); sendBroadcast( updateIntent ); } [/java] Next we need to declare the BroadcastReceiver (which we'll implement as a private inner class of our Activity: [java] public class MainActivity extends FragmentActivity { . . . private class MyServiceReceiver extends BroadcastReceiver { @Override public void onReceive( Context context, Intent intent ) { if ( intent.getAction().equals( MyService.ACTION_PROGRESS ) ) { int progress = intent.getIntExtra( MyService.EXTRA_PROGRESS, 0 ); int max = intent.getIntExtra( MyService.EXTRA_MAX, 0 ); serviceProgress.setMax( max ); serviceProgress.setProgress( progress ); } if ( intent.getAction().equals( MyService.ACTION_UPDATE ) ) { String text = intent .getStringExtra( MyService.EXTRA_UPDATE_TEXT ); serviceProgress.setVisibility( View.INVISIBLE ); serviceButton.setVisibility( View.VISIBLE ); Toast.makeText( MainActivity.this, text, Toast.LENGTH_SHORT ) .show(); } } } . . . } [/java] This will handle the broadcasts that are received and will update the relevant UI components accordingly. We now need to register our BroadcastReceiver. While this can be done in the Manifest, I think that it is better to do it programatically, in this case. If we register and unregister the receiver in the onStart() and onStop() methods of the Activity, then the receiver will only receive broadcasts when the Activity is all or partially visible to the user:

[java] private MyServiceReceiver receiver = new MyServiceReceiver;

@Override
protected void onStart()
{
super.onStart();
IntentFilter filter = new IntentFilter();
filter.addAction( MyService.ACTION_UPDATE );
filter.addAction( MyService.ACTION_PROGRESS );
registerReceiver( receiver, filter );
}

@Override
protected void onStop()
{
unregisterReceiver( receiver );
super.onStop();
}
[/java]

All that remains is to start the service. This can be done by simply calling startService with an appropriate Intent:

[java] startService( new Intent( MyService.ACTION_DO_SOMETHING ) );
[/java]

So we now have a well behaved Service which is capable of updating UI components in an Activity if one is available. I have only really scratched the surface of Services and there are many more subtleties that I haven’t touched on at all.

Hopefully this series has provided some useful techniques in keeping your app responsive which will ultimately help to keep your users happy.

I will leave you with a final thought: I often see apps which display a progress bar immediately after launch while data is being retrieved from the network. I think that the user experience is much better to immediately show the user old data from the last time they used the app and perform the data retrieval in the background before updating the display once we have the fresh data. This certainly introduces some complexity because we need to properly manage how we update the display, just in case we update things just as the user is interacting with it. This certainly requires some careful thought and implementation to get right, and will often lead the developer to revert to the progress bar because it is a lot easier. This is missing a huge point: We are developing apps for the benefit of the user and we should not put the needs of the developer ahead of those of the user. The apps that become the most successful are those which provide the best possible experience for the user and don’t cut corners in order to make the developer’s life easier.

The source code for this article can be found here.

© 2012, Mark Allison. All rights reserved.

Copyright © 2012 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

3 Comments

  1. I’ve used IntentService + BroadcastReceiver for 2way ansynchronous communication with “database service”. 2 things I would add:
    – you don’t have to specify intent-filter for service in manifest if doing like
    Intent i = new Intent(ctx, serviceClass);
    i.setAction(serviceAction);
    – when having multiple instances of activity you must assure that they will handle only their own “broadcast responses”

  2. Thanks for this series, it’s been really helpfu!

    I have a question. If I have two activities in my app, one with listview and one with mapview and they both use the same data (the first one shows the list, the other shows pins on a map) should I use IntentService to provide the data to both of them? If so, should I rather use the started service or bound service approach.

    Thanks in advance!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.