Chapter 7. Scheduling Work with AlarmManager

Maintaining the responsiveness of foreground apps has been our primary focus throughout this book, and we've explored numerous ways to shift work away from the main thread to run in the background.

In all of our discussions so far, we wanted to get the work done as soon as possible, so although we moved it off to a background thread, we still performed the work concurrently with ongoing main thread operations, such as updating the user interface and responding to user interaction.

In this chapter we'll learn to schedule work to run at some distant time in the future, launching our application without user intervention if it isn't already running, and even waking the device from sleep if necessary.

In this chapter we will cover:

In Chapter 3, Distributing Work with Handler and HandlerThread, we learned to schedule work on a HandlerThread using postDelayed, postAtTime, sendMessageDelayed, and sendMessageAtTime. These mechanisms are fine for short-term scheduling of work that should happen soon—while our application is running in the foreground.

However, if we want to schedule an operation to run at some point in the distant future, we'll run into problems. First, our application may be terminated before that time arrives, removing any chance of the Handler running those scheduled operations. Second, the device may be asleep, and with its CPU powered down, it cannot run our scheduled tasks.

The solution to this is to use an alternative scheduling approach—one that is designed to overcome these problems: AlarmManager.

AlarmManager is a system service that provides scheduling capabilities far beyond those of Handler. Being a system service, AlarmManager cannot be terminated and has the capacity to wake the device from sleep to deliver scheduled alarms.

We can access AlarmManager via a Context instance, so from any lifecycle callback in an Activity, we can get the AlarmManager by using the following code:

Once we have a reference to the AlarmManager, we can schedule an alarm to deliver a PendingIntent object at a time of our choosing. The simplest way to do that is using the set method.

When we set an alarm we must also specify a type flag—the first parameter to the set method. The type flag sets the conditions under which the alarm should fire and which clock to use for our schedule.

There are two conditions and two clocks, resulting in four possible type settings.

The conditions specify whether or not the device will be woken up if it is sleeping at the time of the scheduled alarm—whether the alarm is a "wakeup" alarm or not.

The clocks provide a reference time against which we set our schedules, defining exactly what we mean when we set a value to triggerAtMillis.

The elapsed-time system clock—android.os.SystemClock—measures time as the number of milliseconds that have passed since the device booted, including any time spent in deep sleep. The current time according to the system clock can be found using:

The real-time clock measures time in milliseconds since the Unix epoch. The current time according to the real-time clock can be found with:

Given these two conditions and two clocks, the four possible type values we can use when setting alarms are:

Let's consider a few examples. We'll use the TimeUnit class from the java.lang.concurrent package to calculate times in milliseconds. To set an alarm to go off 48 hours after the initial boot, we need to work with the system clock, as shown in the following code:

We can set an alarm to go off in 2 hours from now using a clock, by adding two hours to the current time. Using the system clock it looks like this:

To set an alarm for 2 hours from now using the real-time clock is similar to the previous code:

To set an alarm for 9 p.m. today (or tomorrow, if it's already past 9 p.m. today):

None of the examples so far will wake the device if it is sleeping at the time of the alarm. To do that we need to use one of the WAKEUP alarm conditions, for example:

If our application targets an API level below 19 (KitKat), alarms scheduled with set will run at exactly the alarm time. For applications targeting KitKat or greater, the schedule is considered inexact and the system may re-order or group alarms to minimize wake-ups and save battery.

If we need precision scheduling and are targeting KitKat or greater, we can use the new setExact method introduced at API level 19, but we'll need to check that the method exists before we try to call it; otherwise, our app will crash when run under earlier API levels:

This will deliver our alarm at exactly the specified time on all platforms. We should only use exact scheduling when we really need it—for example, to deliver alerts to the user at a specific time. For most other cases, allowing the system to adjust our schedule a little to preserve battery life is usually acceptable.

One more addition in KitKat is setWindow, which introduces a compromise between exact and inexact alarms by allowing us to specify the time window within which the alarm must be delivered. This still allows the system some freedom to play with the schedules for efficiency, but lets us choose just how much freedom to allow.

Here's how we would use setWindow to schedule an alarm to be delivered within a 3 minute window—at the earliest 10 minutes from now and at the latest 13 minutes from now—using the real-time clock:

As well as setting a one-off alarm, we have the option to schedule repeating alarms using setRepeating and setInexactRepeating. Both methods take an additional parameter that defines the interval in milliseconds at which to repeat the alarm. Generally it is advisable to avoid setRepeating and always use setInexactRepeating, allowing the system to optimize wake-ups and giving more consistent behavior on devices running different Android versions:

AlarmManager provides some handy constants for typical repeat intervals:

We can schedule a repeating alarm to be delivered approximately 2 hours from now, then repeating every 15 minutes or so thereafter like this: