Chapter 6. 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 and run work 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 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 will learn how to defer work with AlarmManager
to run at some distant time in the future, initiating work without user intervention, and even waking up the device from an idle state if it is really necessary. Meanwhile, we will introduce you to some power saving features introduced with Android Marshmallow 6 and explain how to adapt your application to this new paradigm.
In this chapter we will cover the following topics:
In Chapter 2, Performing Work with Looper, Handler, and HandlerThread, we learned how to schedule work on a HandlerThread
using postDelayed
, postAtTime
, sendMessageDelayed
, and sendMessageAtTime
. These mechanisms are fine for short-term scheduling of work 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.
Note
The solution to this is to use an alternative scheduling approach, one that is designed to overcome these problems: AlarmManager
.
android.app.AlarmManager
is a class that has been available in the Android SDK since the first version, delivering an advanced API to fire off Intents in the future at one specific time...
Scheduling alarms with AlarmManager
As we said before, all the alarm operations are managed through the singleton object AlarmManager
, an Android global system service that can be retrieved by any class with access to a Context
instance. As an example, in an Activity
we can get the AlarmManager
from any member method by using the following code:
Once we have a reference to the AlarmManager
, we can schedule an alarm to deliver a PendingIntent
object to a Service
, an Activity
or BroadcastReceiver
, 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 first...
Once the alarm is set, it can be canceled very easily by invoking the AlarmManger.cancel
method with an intent that matches the alarm that we want to cancel.
The process of matching uses the filterEquals
method of Intent, which compares the action, data, type, class, component, package, and categories of both Intent
to test for equivalence. Any extras we may have set in the Intent are not taken into account.
In the following code, we will show you how to create an alarm that fires off in 1 hour and the cancel code to dismiss it using different intent instances:
Scheduling repeating alarms
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 device wake-ups and giving more consistent behavior on devices running different Android versions:
AlarmManager
provides some handy constants for typical repeat intervals:
Let's now build up an example that...
Scheduling an alarm clock
From API Level 21, setAlarmClock
, which sets a new alarm and displays a status bar alarm icon, was introduced in the AlarmManager
class:
In the next example we are going to create an alarm clock that goes off tomorrow at 10:00 pm:
So far we have learned how to schedule exact and inexact alarms over the AlarmManager Service
singleton, so at this point we are ready to take a look at how to handle the alarm in any Android application component.
Essentially, we can schedule anything that can be started with a PendingIntent
, which means we can use alarms to start Activities, Services, and BroadcastReceivers
. To specify the target of our alarm, we need to use the static factory methods of PendingIntent
:
All static methods offered to create a pending intent, receiving as arguments a Context object, an integer request code to identify the pending intent, an Intent or an array of Intents that will be delivered to the component, and finally an integer to specify the PendingIntent
flags.
The PendingIntent
...
Handling alarms with Activities
Starting an Activity
from an alarm is as simple as registering the alarm with a PendingIntent
created by invoking the static getActivity
method of PendingIntent
.
When the alarm is delivered, the Activity
will be started and brought to the foreground, displacing any app that was currently in use. Keep in mind that this is likely to surprise and perhaps annoy users!
When starting Activities with alarms, we will probably want to set Intent.FLAG_ACTIVITY_CLEAR_TOP
; so that if the application is already running, and our target Activity
is already on the back stack, the new intent will be delivered to the old Activity
and all the other activities on top of it will be closed:
Not all Activities are suited to being started with getActivity
. We...
Handling alarms with BroadcastReceiver
We met BroadcastReceiver
already in Chapter 5, Interacting with Services, where we used it in an Activity
to receive broadcasts from a Service
. In this section, we'll use BroadcastReceiver
to handle alarms set on the AlarmManager
.
BroadcastReceivers
can be registered and unregistered dynamically at runtime like we did in Chapter 5, Interacting with Services, with Service
, or statically in the Android manifest file with a <receiver>
element, and can receive alarms regardless of how they are registered.
It is more common to use a statically registered receiver for alarms, because these are known to the system and can be invoked by alarms to start an application if it is not currently running.
Let's implement a static defined BroadcastReceiver
that is able to dispatch an SMS to a phone number when an alarm sounds. First we will define our BroadcastReceiver
in the manifest file:
Handling alarms with Services
Just like starting Activities, starting a Service from an alarm involves scheduling an appropriate PendingIntent
instance, this time using the static getService
method:
As you already know, the Service should be globally defined on the Android Manifest with a service element. Given that we are calling it explicitly using the class name, we only need to define the service class:
We almost certainly want our Service to do its work off the main thread, so sending work to an IntentService
this way seems ideal, and an IntentService
will also...
Resetting alarms after a system reboot
The AlarmManager
service is a convenient class to schedule working on your Android application; however, when the device shuts down or reboots, all your alarms will be lost since the system does not retain them between system restarts.
To reset the alarm, we should persist your alarms and create a BroadcastReceiver
that sets our alarms whenever a system boot happens:
In order to store our alarms, we created a POJO
class SMSSchedule
as the model for our schedules.
Second, in the Android Manifest we have to register our BroadcastReceiver
to receive the boot event:
Applications of AlarmManager
AlarmManager
allows us to schedule work to run without user intervention.
This means that we can arrange to do work pre-emptively, for example, to prepare data that our application will need to present to the user when they next open the application, or to alert the user to new or updated information with notifications.
Ideal use cases include things like periodically checking for new e-mails, SMS scheduling, time notifications, periodic data processing, downloading new editions of periodical publications (for example, daily newspapers and magazines), or uploading data from the device to a cloud backup service.
The AlarmManager
is able to start future work effectively but the API should be used carefully to keep your application battery power consumption at low levels. To achieve that, the developer should try to keep the alarm frequency under certain levels and use the exact set functions that force the device to wake up only in cases where it is really necessary...
In this chapter, we learned to schedule work for our applications to perform at some time in the distant future, either as a one-shot operation or at regular intervals.
We learned to set alarms relative to the system clock or real time, how to wake the device up from a deep sleep and doze mode, how to cancel alarms when we no longer need them, and how to set exact alarms on the most recent Android versions.
In the meantime, we introduced the reader to Doze Mode, a new power management feature that saves battery cycles by deferring jobs and tasks to a maintenance window. We learned how to test our alarms taking into account the new power management states introduced by the doze mode.
We learned how to debug alarms created with AlarmManager
and how to analyze the information printed from the dumpsys
commands.
Our exploration covered various options for responding to alarms, including bringing an Activity
to the foreground or doing work directly in a BroadcastReceiver
, synchronously or...