The IntentService
class is a specialized subclass of Service
that implements a background work queue using a single HandlerThread
. When work is submitted to an IntentService
, it is queued for processing by a HandlerThread
, and processed in order of submission.
If the user exits the app before the queued work is completely processed, the IntentService will continue working in the background. When the IntentService has no more work in its queue, it will stop itself to avoid consuming unnecessary resources.
The system may still kill a background app with an active IntentService, if it really needs to (to reclaim enough memory to run the current foreground process), but it will kill lower priority processes first, for example, other non-foreground apps that do not have active services.
The IntentService
class gets its name from the way in which we submit work to it: by invoking startService
with an Intent.
startService(new Intent(context, MyIntentService.class));
We can call startService
as often as we like, which will start the IntentService if it isn't already running, or simply enqueue work to an already running instance if there is one.
If we want to pass some data to a Service, we can do so by supplying a data Uri
or extra data fields via an Intent.
Intent intent = new Intent(context, MyIntentService.class); intent.setData(uri); intent.putExtra("param", "some value"); startService(intent);
We can create an IntentService
subclass by extending android.app.IntentService
and implementing the abstract onHandleIntent
method. We must invoke the single-argument constructor with a name for its background thread (naming the thread makes debugging and profiling much easier).
public class MyIntentService extends IntentService { public MyIntentService() { super("thread-name"); } protected void onHandleIntent(Intent intent) { // executes on the background HandlerThread. } }
We'll need to register the IntentService in our AndroidManifest
file, using a <service>
element as follows:
<service android:name="com.mypackage.MyIntentService"/>
If we want our IntentService
to only be used by the components of our own application, we can specify that it is not public
with an extra attribute:
<service android:name=".MyIntentService"
android:exported="false"/>
Let's get started by implementing IntentService
to do something we're already familiar with—calculating the nth prime:
public class PrimesIntentService extends IntentService { public static final String PARAM = "prime_to_find"; public PrimesIntentService() { super("primes"); } protected void onHandleIntent(Intent intent) { int n = intent.getIntExtra(PARAM, -1); BigInteger prime = calculateNthPrime(n); } private BigInteger calculateNthPrime(int n) { BigInteger prime = BigInteger.valueOf(2); for (int i=0; i<n; i++) prime = prime.nextProbablePrime(); return prime; } }
Notice that we're declaring a public static
constant name for the parameter, just to make it easy to use the correct name from any client Activity that wants to invoke the Service. We can invoke this IntentService
to calculate the nth prime as follows:
private void triggerIntentService(int primeToFind) { Intent intent = new Intent(this, PrimesIntentService.class); intent.putExtra(PrimesIntentService.PARAM, primeToFind); startService(intent); }
So far so good, but you've probably noticed that we haven't done anything with the result we calculated. In the next section, we'll look at some of the ways in which we can send results from services to activities or fragments.
Any Service
—including subclasses of IntentService
—can be used to start background work from which the originating Fragment
or Activity
doesn't expect a response.
However, it is very common to need to return a result or display the result of the background work to the user. We have several options for doing this:
PendingIntent
to the Service from the originating Activity, allowing the Service to callback to the Activity via its onActivityResult
methodMessage
to a Handler
in the originating Activity using MessengerIntent
, allowing any Fragment
or Activity
—including the originator—to receive the result of background processingWe'll learn about Messenger
and BroadcastReceiver
in Chapter 6, Long-running Tasks with Service. In this chapter, we'll return results with PendingIntent
, and alert the user with system notifications.
When we invoke an IntentService
, it does not automatically have any way to respond to the calling Activity
; so if the Activity wants to receive a result, it must provide some means for the IntentService
to reply.
Arguably, the easiest way to do that is with PendingIntent
, which will be familiar to any Android developer who has worked with multiple activities using the startActivityForResult
and onActivityResult
methods, as the pattern is essentially the same.
First, we'll add a few static members to PrimesIntentService
to ensure that we use consistent values between it and the calling Activity
:
public static final String PENDING_RESULT = "pending_result"; public static final String RESULT = "result"; public static final int RESULT_CODE = "nth_prime".hashCode();
We'll also need to define a static member in our Activity
for the REQUEST_CODE
constant, which we can use to correctly identify the results returned to our onActivityResult
method:
private static final int REQUEST_CODE = 0;
Now, when we want to invoke PrimesIntentService
from our Activity
, we'll create a PendingIntent
for the current Activity
, which will act as a callback to invoke its onActivityResult
method.
We can create a PendingIntent
with the createPendingResult
method of Activity
, which accepts three parameters: an int
result code, an Intent to use as the default result, and an int
that encodes some configuration for how the PendingIntent
can be used (for example, whether it may be used more than once).
PendingIntent pending = createPendingResult( REQUEST_CODE, new Intent(),(), 0);
We pass the PendingIntent
to the IntentService
by adding it as an extra in the Intent we launch the IntentService
with:
private void triggerIntentService(int primeToFind) { PendingIntent pending = createPendingResult( REQUEST_CODE, new Intent(), 0); Intent intent = new Intent(this, PrimesIntentService.class); intent.putExtra(PrimesIntentService.PARAM, primeToFind); intent.putExtra( PrimesIntentService.PENDING_RESULT, pending); startService(intent); }
To handle the result that will be returned when this PendingIntent
is invoked, we need to implement onActivityResult
in the Activity, and check for the result code:
protected void onActivityResult(int req, int res, Intent data) { if (req == REQUEST_CODE && res == PrimesIntentService.RESULT_CODE) { BigInteger result = (BigInteger) data.getSerializableExtra(PrimesIntentService.RESULT); // … update UI with the result } super.onActivityResult(requestCode, resultCode, data); }
The IntentService
can now reply to the calling Activity
by invoking one of the PendingIntent's send
methods with the appropriate request code. Our updated onHandleIntent
method looks as follows:
protected void onHandleIntent(Intent intent) { int n = intent.getIntExtra(PARAM, -1); BigInteger prime = calculateNthPrime(n); try { Intent result = new Intent(); result.putExtra(RESULT, prime); PendingIntent reply = intent.getParcelableExtra(PENDING_RESULT); reply.send(this, RESULT_CODE, result); } catch (PendingIntent.CanceledException exc) { Log.i(TAG, "reply cancelled", exc); } }
The additional code creates a new Intent
object and populates it with our calculated prime, then sends it back to the calling Activity
using PendingIntent
. We also have to handle the CanceledException
, in case the calling Activity decided that it wasn't interested in the result any more and cancelled the PendingIntent
. That's all there is to it—our Activity
will now be invoked via its onActivityResult
method when the IntentService
completes its work.
As a bonus, we will still receive the result if the Activity
has restarted, for example, due to configuration changes such as a device rotation.
What if the user left the Activity
(or even left the application) while the background work was in progress? In the next section, we'll use notifications to provide feedback without interrupting the user's new context.
System notifications appear initially as an icon in the notification area, normally at the very top of the device screen. Once notified, the user can open the notification drawer to see more details.
Notifications are an ideal way to inform the user of results or status updates from services, particularly when the operation may take a long time to complete and the user is likely to be doing something else in the meantime.
Let's post the result of our prime number calculation as a notification, with a message containing the result that the user can read when they open the notification drawer. We'll use the support library to ensure broad API level compatibility, and add one method to PrimesIntentService
as follows:
private void notifyUser(int primeToFind, String result) { String msg = String.format( "The %sth prime is %s", primeToFind, result); NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.prime_notification_icon) .setContentTitle(getString(R.string.primes_app)) .setContentText(msg); NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nm.notify(primeToFind, builder.build()); }
Here we're using NotificationCompat.Builder
to build a notification that includes an icon, a title (just the name of our application), and a message containing the result of the calculation.
Each notification has an identifier, which we can use to control whether a new notification is posted or an existing one is reused. The identifier is an int
, and is the first parameter to the notify
method. Since our primeToFind
value is an int
, and we would like to be able to post multiple notifications, it makes sense to use primeToFind
as the ID for our notifications so that each different request can produce its own separate notification.
To post a notification containing the result of our calculation, we just need to update onHandleIntent
to invoke the notifyUser
method:
protected void onHandleIntent(Intent intent) {
int n = intent.getIntExtra(PARAM, -1);
BigInteger prime = calculateNthPrime(n);
notifyUser(n, prime.toString());
}
Now that we've learned the basics of using IntentService
, let's consider some real-world applications.
13.58.96.83