Android AlarmManager As Deep As Possible

AlarmManager
is a bridge between application and Android system alarm service. It can send a broadcast to your app (which can be completely terminated by user) at a scheduled time and your app can then perform any task accordingly.
With the help from PendingIntent
and Intent
, a bundle of information can also be sent together with the system broadcast to your application when alarm goes off.
However, since Android KitKat (API 19) and Android MarshMallow (API 23), Google has added different restrictions on AlarmManager
to reduce the battery usage. Alarm no longer goes off exactly at the assigned time by default. System has the ability to defer any alarm in order to optimise the device performance.
It is a common trap to developers that AlarmManager
does not keep a record to all the scheduled alarms after device is booted. Application has to register all the alarms again every time the device is booted.
Let’s start the introduction and discussion about AlarmManager
below.
Setup
There are two main parts for setting up an alarm. They are
- Create a
BroadcastReceiver
to receive system broadcast and register it at theAndroidManifest.xml
- Register an alarm with an alarm type,
PendingIntent
and designated time to the systemALARM_SERVICE
Step 1: Set up BroadcastReceiver — Handle System alarm broadcast
BroadcastReceiver
is a class for receiving and handling the broadcast sent to your application. In system alarm case, it receives broadcast from the system alarm service when alarm goes off. It has to be either registered dynamically in activity or statically declared at AndroidManifest.xml
. The above codes show an example to register at AndroidManifest
statically.
The most important function to be overridden in a BroadcastReceiver
is onReceive()
. It is the place for receiving the broadcast sent from the system. Developer can manipulate the data bundled in the received Intent
by using the getXXXExtra()
functions.
It is strongly recommended to check the action
field in Intent
to ensure the broadcast is coming from your assigned PendingIntent
.
Step 2: Setup in Activity
Here is an all-in-one code snippet and will be described part-by-part:
Step 2 — Part 1 : Get AlarmManager instance
As mentioned at the start of this article, AlarmManager
is actually a system service and thus can be retrieved from Context.getSystemService()
function with parameter Context.ALARM_SERVICE
.
Step 2 — Part 2 : Prepare for an Intent
Android allows a bundle of information to be sent to a target receiver
(i.e. AlarmReceiver
, which is defined in step 1) when alarm goes off. The designated receiver and the corresponding information can be set in Intent
by setting its action
and extra
. These information can later be retrieved at the onReceive()
callback in the designated BroadcastReceiver
and action
field is checked to ensure the correctness of the system broadcast.
Step 2 — Part 3 : Prepare for a PendingIntent
PendingIntent
is a reference pointing to a token maintained by the Android system. Thus, it will still be valid if the application is killed by user and can be broadcasted at some moment in the future.
There are totally 4 functions for initialising a PendingIntent
but only 1 of them is applicable:
PendingIntent.getBroadcast()
— Applicable toAlarmManager
PendingIntent.getActivity()
PendingIntent.getActivities()
PendingIntent.getService()
As mentioned before, AlarmManager
will send a broadcast to the registered BroadcastReceiver
( AlarmReceiver
in this example). By official documentation, only getBroadcast()
is applicable for AlarmManager.
Retrieve a PendingIntent that will perform a broadcast, like calling
Context#sendBroadcast(Intent)
.Reference: Google official documentation — PendingIntent.getBroadcast()
The request code can be treated as an identifier for different PendingIntent
tokens with the same Intent
. In other words, it is only useful when you want multiple PendingIntent
to have the same Intent
.
Determine if two intents are the same for the purposes of intent resolution (filtering). That is, if their action, data, type, identity, class, and categories are the same. This does not compare any extra data included in the intents.
Reference: Google official documentation — Intent.filterEquals()
Flags indicates how system should handle the new and existing PendingIntent
s that have the same Intent
. 0
indicates that system will use its default way to handle the creation of PendingIntent
. The following are some examples:
- FLAG_UPDATE_CURRENT
- FLAG_CANCEL_CURRENT
- FLAG_IMMUTABLE
Step 2 — Part 4 : Set the alarm time and send to system
The simplest way to set an alarm is using setExactAndAllowWhileIdle()
with parameter RTC_WAKEUP
. This would tell Android fires the alarm exactly at the assigned time no matter the system is in doze mode (idle mode) which will be discussed later.
Handling of alarm event
AlarmManager
provides two ways to listen to an alarm broadcast. They are
- Local listener (AlarmManager.OnAlarmListener)
BroadcastReceiver
specified at theIntent
wrapped inside aPendingIntent
.
The implementation of AlarmManager.OnAlarmListener
is similar to the one using PendingIntent
, instead it requires a callback and its corresponding Handler
:
There is a limitation on AlarmManager.OnAlarmListener
over PendingIntent
. It cannot work when the corresponding Activity
or Fragment
is destroyed since the callback object is released at the same time. However, because PendingIntent
is sent to the system alarm service, it can be fired even when the application is killed by user.
Way to set alarm (October, 2013)
AlarmManager.set
becomes interpreted as an inexact value, to give the system more flexibility in scheduling alarms.Reference: Google Android KitKat changes
Since the release of Android KitKat (Android 4.4 — 4.4.4 / API 19) in October, 2013, Android has a control over how the alarm service is scheduled. It shifts alarms in order to minimise the number of device wakeup and reduce the battery usage. For then, there are two different functions for setting an alarm:
set
(int type, long triggerAtMillis, PendingIntent operation)setExact
(int type, long triggerAtMillis, PendingIntent operation)
According to their names, set()
will let Android interrupt the scheduled time and setExact()
will ask Android to fire exactly at what the requested time is.
Ways to set alarm (October, 2015)

Two years after the release of Android KitKat, there are 2 new operation modes introduced to Android M (Android 6.0 / API 23). They are Doze
and App Standby
. They both further interrupt the scheduled alarm when device is not plugged in with a power supply.
When user turns screen off and does not plug in any power supply, device goes to Doze
mode. In Doze
mode (green part in the above graph), Android restricts app from accessing to the network and CPU-intensive services. Thus, the scheduled alarm will be suspended and deferred. Android will periodically exit and enter Doze
mode periodically and the in between period is called the maintenance window
(orange part in the above graph). Maintenance window
releases all the restrictions set in Doze
mode. In other words, network access is given back and the scheduled tasks are fired.
App Standby
mode is similar to the Doze
mode except the screen is not required to be off. It restricts app from accessing to the network and defers the scheduled tasks. Android would consider an app to be idle when the app is sent to the background for a certain period of time with no foreground service and does not generate any notification. Moreover, active device admin app (such as device policy controller) is an exception.
There are a list of adb
(Android Device Bridge) commands for forcing a phone into and outDoze
and App Standby
mode manually.
Alarm reference time
AlarmManager accepts two types of time to fire an alarm:
- Real Time Clock (RTC)
- Elapsed Real Time
Real Time Clock is the absolute time since January 1, 1970 UTC and Elapsed Real Time is the relative time since the device is booted.
By default, AlarmManager fires an alarm only when device is not sleeping. The types are RTC
and ELAPSED_REALTIME
. In order to have an alarm goes off during device is sleeping, the type of alarm must be set to either RTC_WAKE_UP
or ELAPSED_REALTIME_WAKEUP
.
To cancel an alarm
To cancel a listener, simply save a global instance of listener and cancel it with the below function:
However, it is tricky to cancel a PendingIntent
since it depends on both the Intent
and requestCode
used.
We first need to understand how Android determines if two Intent
s are the same. Two Intent
s are the same only when their action
, data
, type
, identity
, class
and categories
are all the same, not including the extra
that we always put information into.
We should not mix up data
with extra
. data
(e.g. tel:12345678
) is an Uri
which operates with action
(e.g. ACTION_DIAL
). extra
is a bundle of information that be sent to the target receiver. It is represented as key-value pairs.
Therefore, the below Intent
s are the same even the extra
are different:
The second trick is the requestCode
which is an identifier Intent
s with the same details. In order to retrieve the same PendingIntent
object from the system, the requestCode
must be correct.
Finally the alarm with PendingIntent
can be cancelled with the following codes:
To set a repeating alarm
It is simple to set an alarm repeating with certain interval with the above codes. However, there are some tricks on it.
To preserve battery life, Android has changed the policy on handling repeating alarm since Android 5.1 (API 22). It delays the alarm at least 5 seconds into the future and limit the repeat interval to at least 60 seconds.
Therefore, the above sample code will eventually be equal to this after API 22:
In order to overcome the “5 second” delay on alarm, we should better use Handler
. However, unlike PendingIntent
alarm, it can no longer function when application is killed by user.
To set a window of alarm time
setWindow (type: Int, windowStartMillis: Long, windowLengthMillis: Long, operation: PendingIntent)
Beyond set()
and setExact()
, there is setWindow()
which sits between set()
and setExact()
. It let Android defer alarm to optimise battery usage but guarantees the alarm will be fired within the given window of time.
The following example shows an alarm which will go off between 00:00 to 00:01 on 2019-Jan-01:
Reset alarm
Android does not save any alarms when the device is turned off. Therefore, all alarms will be missed when device is booted. In order to maintain all the previously set alarms, we have to listen to the system boot event and manually set back all the alarms when device is turned on again.
Every time when Android device is booted and user has unlocked the device, system broadcast an event android.intent.action.BOOT_COMPLETED
to all applications. By listening to the this system event , application can then be notified and reset the alarms. Be aware that listening to android.intent.action.BOOT_COMPLETED
is considered as a dangerous operation and the <uses-permission>
of android.permission.RECEIVE_BOOT_COMPLETED
must also be added in AndroidManifest.xml
as required by Android.
Debug checklist
Here is a checklist when the assigned alarm is no fired at your designated time:
- Check if
Receiver
is registered at theAndroidManifest.xml
within the<application>
tag. - Check if
PendingIntent.getBroadcast()
is used since this is the onlyPendingIntent
function used for sending system broadcast. - Check if
WAKE_UP
type (RTC_WAKE_UP
orELAPSED_REALTIME_WAKEUP
)is chosen since Android does not wake up device by default. - Check if
setExactAndAllowWhileIdle()
is used since Android would intercept the actual alarm time by default after API 23.
Summary
Android provides an alarm service to notify application at any specific time with the corresponding assigned information stored in Intent
.
Along with the evolution of Android system throughout the years, Android applies more controls of alarm service to optimise the battery consumption.
setExactAndAllowWhileIdle()
together with WAKE_UP
type should only be used when the alarm time must be as exact as possible.
App developer should bear in mind to re-register all the alarms again when device is booted since Android does not store any alarms by default.
Hope this article can bring something to you! 😉 See you next time~
Further readings:
1. AlarmManager official documentation
2. Understanding Doze and App Standby mode
Please follow me at Twitter@myrick_chow for more information. Thank you for reading this article. Have a nice day! 😄