Yesterday I was short on time and wasn’t able to complete the talk about Intents as I had hoped which means that today we’ll wrap up talking about them as we continue on with the 31 Days of Android. The last thing we’re going to talk about is using Intents to start services and then updating your UI when those services finish.
Using Services and Intents
The second topic I’d like to cover today that is related to Intents is creating background services that are capable of updating your UI. You’ll see that the service will be kicked off by sending an Intent. Your service will update the UI via another messaging method called a ResultReceiver. There are several steps you’ll have to go through to make this work so let’s get started. First, right click on src/com.daytwentyseven and choose New -> Class. Name your class “DayTwentySevenService” and set it’s superclass to “android.app.IntentService”. This will create a new class with a onHandleIntent method for you to implement. In order for this to compile you’ll need to at least add a constructor which should call the super constructor:
public class DayTwentySevenService extends IntentService {
public DayTwentySevenService() {
super("DayTwentySevenService");
}
@Override
protected void onHandleIntent(Intent arg0) {
// TODO Auto-generated method stub
}
}
Note that the super constructor expects a String argument to be passed in which is the name of the service. It’s only used for debugging purposes but it may be something you want to define in a constant for the class. There are a number of additional member variables you’ll want to add to your class:
// Status Constants
public static final int STATUS_RUNNING = 0x1;
public static final int STATUS_FINISHED = 0x2;
public static final int STATUS_SUCCESS = 0x3;
public static final int STATUS_ERROR = 0x4;
// Command Constants
public static final int PERFORM_SERVICE_ACTIVITY = 0x5;
public static final String COMMAND_KEY = "service_command";
public static final String RECEIVER_KEY = "serivce_receiver";
public static final String SERVICE_WAS_SUCCESS_KEY = "service_was_success";
private ResultReceiver receiver;
Strictly speaking, you don’t need to use these public static final variables, however, since you need to reference these values in more than one class, this is a better way to handle it (as opposed to using inline values). Now you need to implement your onHandleIntent method:
@Override
protected void onHandleIntent(Intent intent) {
this.receiver = intent.getParcelableExtra(RECEIVER_KEY);
int command = intent.getIntExtra(COMMAND_KEY, PERFORM_SERVICE_ACTIVITY);
this.receiver.send(STATUS_RUNNING, Bundle.EMPTY);
switch (command) {
case PERFORM_SERVICE_ACTIVITY:
doServiceStuff(intent);
break;
default:
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
}
this.stopSelf();
}
Here you’re setting your ResultReceiver to a value passed in via the intent object. Then you’re getting the command from the same intent. Then you’re sending a message back to the receiver telling it that your service is running. Finally, provided the correct command was sent in, you’ll perform whatever operations the service is meant to do via the doServiceStuff method:
private void doServiceStuff(Intent intent) {
for (int i = 0; i < 100000; i++) {
String s = "This " + "is " + "a " + "test";
}
if (false) { // error
receiver.send(STATUS_ERROR, Bundle.EMPTY);
this.stopSelf();
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
} else {
Bundle b = new Bundle();
b.putBoolean(SERVICE_WAS_SUCCESS_KEY, true);
receiver.send(STATUS_SUCCESS, b);
this.stopSelf();
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
}
}
This is where you WOULD do whatever processing you needed to do. Instead, you’re just going through a loop to kill some time so the response isn’t sent back right away. Examples of common stuff you might do here is hit a web service, do extensive backend processing, or anything else that you wouldn’t want tying up the UI thread. Once that is done, if everything was a success, you’ll send a message to receiver saying you things were a success and then finished or if there was a failure, you can send back that there was an error and that the service is finished. Now before you can start making changes to your DayTwentySevenActivity to start the service and handle results, you’ll need to create one more class. Right click on src/com.daytwentyseven and go to New --> Class. Name your class ServiceResultReceiver and set it’s subclass to android.os.ResultReceiver. This class is going to be generic and could be used for any class that you want to receive service results. Let’s look at the code:
public class ServiceResultReceiver extends ResultReceiver {
private Receiver mReceiver;
public ServiceResultReceiver(Handler handler) {
super(handler);
}
public void setReceiver(Receiver receiver) {
mReceiver = receiver;
}
public interface Receiver {
public void onReceiveResult(int resultCode, Bundle resultBundle);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultBundle) {
if (mReceiver != null) {
mReceiver.onReceiveResult(resultCode, resultBundle);
}
}
}
As you can see, you haven’t put any code in this class that is specific to the service you created or any activities in your project. Indeed, you could reuse this class in the exact same format across your projects. Now open the src/com.daytwentyseven/DayTwentySevenActivity.java class and make it implement ServiceResultReceiver.Receiver:
public class DayTwentySevenActivity extends Activity implements ServiceResultReceiver.Receiver {
In order to properly implement the ServiceResultReceiver, you’ll need to implement the onReceiveResult method:
public void onReceiveResult(int resultCode, Bundle resultBundle) {
switch (resultCode) {
case DayTwentySevenService.STATUS_RUNNING:
// Don't do anything, the service is running
break;
case DayTwentySevenService.STATUS_SUCCESS:
boolean wasSuccess = resultBundle
.getBoolean(DayTwentySevenService.SERVICE_WAS_SUCCESS_KEY);
if (wasSuccess) {
Toast.makeText(getApplicationContext(),
"The service was a success", Toast.LENGTH_LONG).show();
} else {
// Show not success message
Toast.makeText(getApplicationContext(),
"The service was a failure", Toast.LENGTH_LONG).show();
}
break;
case DayTwentySevenService.STATUS_FINISHED:
Toast.makeText(getApplicationContext(), "The service was finished",
Toast.LENGTH_LONG).show();
break;
case DayTwentySevenService.STATUS_ERROR:
Toast.makeText(getApplicationContext(), "The service had an error",
Toast.LENGTH_LONG).show();
break;
}
}
Here you’re checking the result code sent from the service and depending on which constant in the DayTwentySevenService class it matches, you’re showing a Toast depending on whether it’s running, was successful, was a failure, had an error, or was finished. You could do any UI changes you want here. For example, if your service had pulled data down from the internet, you could now display it in your Activity. The last thing you need to address is starting the service. To start, add a private variable to your class for your ServiceResultReceiver:
private ServiceResultReceiver receiver;
Then in the onCreate method you need to set your receiver variable:
// Set our receiver
receiver = new ServiceResultReceiver(new Handler());
receiver.setReceiver(this);
Next, you need to start the service. This is done by sending an intent. Let’s add this in for the button2 onClickListener:
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final Intent serviceIntent = new Intent(Intent.ACTION_SYNC, null,
getApplicationContext(), DayTwentySevenService.class);
// put the specifics for the submission service commands
serviceIntent.putExtra(DayTwentySevenService.RECEIVER_KEY, receiver);
serviceIntent.putExtra(DayTwentySevenService.COMMAND_KEY, DayTwentySevenService.PERFORM_SERVICE_ACTIVITY);
//Start the service
startService(serviceIntent);
}
});
Here you’re making more use of those constants you defined in the DayTwentySevenService class to put the receiver and command keys in as extras sent through the intent. This isn’t all that different from any of the other intents you’ve used. Now if you run your application and tap on the second button you won’t see anything happen. If you check LogCat you’ll see a message that looks like this:
Unable to start service Intent { act=android.intent.action.SYNC
cmp=com.daytwentyseven/.DayTwentySevenService (has extras) }: not found
The problem here is that the Android OS hasn’t been told about your service. To do this, you need to add the service into your manifest file:
<service android:name=".DayTwentySevenService"/>
Now when you run your app and tap on the second button, after a few seconds you should see the Toast appear saying it’s been a success:
Again, this demo is very simplified but you could do anything you want here. Commonly you might pull back data from a network resource in your service and then once it’s received you would use this technique to update the UI with your data.
You can download the final working code from today (and yesterday) here.