Simplifying AlarmManager: Understanding Alarm Scheduling in Android

ยท

8 min read

After reading the official documentation of AlarmManager, I found it a little hard to understand. This is my attempt to simplify it and explain things more easily. I hope you enjoy it.

WorkManager: "My Documentation is difficult to get"

Before beginning, let us first understand a concept that will come in handy at every step while learning about scheduling alarms with AlarmManager.

Behold the Doze mode ๐Ÿ˜ด

Introduced in Android 6.0 (API level 23), Doze mode optimizes the battery life of the device by restricting its access to network and CPU work when the device is not actively used.

Great for Android device users ๐Ÿ‘Œ but a nightmare for Android developers ๐Ÿ˜ซ.

Doze mode causes flaky behavior of the features that depend on the AlarmManager. Why so? Well, as discussed above, network access and CPU usage are restricted. This means that if the device is in Doze mode and an alarm is scheduled during that period, the alarm will not fire and instead get deferred. These deferred alarms can fire either when the device exits Doze mode or during the maintenance windows offered during Doze mode itself.

These maintenance windows exist so that work doesn't get deferred to a very late time. Hence, even in the stationary state, the device exits Doze mode for a while, does the deferred work, and enters back into the Power-saving/Doze State. We will still see how to set alarms that can fire even in Doze mode later in the article.
For a device to be eligible for entering Doze mode, all of the below-given conditions must be satisfied:

  1. The device is not connected to the charger.

  2. The device's screen is off.

  3. The device is stationary for a while.

If all these conditions are true, the Android OS puts the device in Doze mode.

There is another power-saving feature called App Standby, which was introduced along with Doze mode. This mode is concerned with putting the app in the power-saving/idle state when the app is neither in the foreground nor doing any work visible to the user, such as sending notifications on the lock screen or notification tray. Again, as this mode is concerned with saving power, if the device is connected to the charger, this mode won't come into the picture.

Firing an Alarm is considered to be a system-intensive and battery-consuming operation.

Types of alarms that can be set by using the AlarmManager API

  1. Inexact alarms: These alarms are guaranteed to fire, but the system takes the liberty to schedule them in the future if their firing time coincides with the time when the device is in Doze Mode.

  2. Exact alarms: Alarms that need to fire at the precise time, irrespective of whether the device is in Doze mode.

Discussing the steps on how to set an alarm, such as setting up a BroadcastReceiver and creating a PendingIntent, will make this article really lengthy. Hence, I have not discussed them here, as the article is about understanding the nuances of scheduling alarms with AlarmManager and not about setting up an alarm in general. You can refer to this video from Philipp Lackner's YouTube channel to learn the same

Methods to set Inexact Alarms

  1. set()
public void set(int type, 
            long triggerAtMillis, 
            PendingIntent operation)

An alarm set by this method won't fire if the device is in Doze/idle mode. Two out of the three arguments it takes are triggerAtMillis, which is the time at which you want the alarm to fire and operation is the PendingIntent which basically is used to execute the code of an app when the app's process itself is not present in the memory.
The first argument, type is the clock type, which is discussed next.

These three arguments are almost common for every method by which an alarm, both Exact or Inexact, can be set.

Types of Clock

When setting an Alarm, you have to specify what type of clock the alarm is based on. By type of clock, I mean whether the alarm depends on the actual clock of the device, which corresponds to type RTC (Real-Time Clock), or it should just consider the time since the device was booted up, which corresponds to ELAPSED_REALTIME.

For example, if an Alarm fires every 2 hours irrespective of the time on the clock, then the ELAPSED_REALTIME clock can be used. Both these clocks have wake-up versions, RTC_WAKEUP and ELAPSED_REALTIME_WAKEUP.
WAKE_UP signifies that the device's CPU will wake up when the alarm is fired to convey to the user that the alarm has fired, such as by playing a sound or vibrating, depending upon the device's setting.

WAKE_UP doesn't explicitly signify that the device's screen will turn on. It just means that the CPU will turn on to facilitate other operations that can convey to the user that the alarm has gone off.

Now that we have understood what Clock Types are, we can continue with the methods to set Inexact alarms.

  1. setAndAllowWhileIdle()
public void setAndAllowWhileIdle(
            int type, 
            long triggerAtMillis, 
            PendingIntent operation)

This method takes the same parameters as set(), but it guarantees that the alarm will fire even if the device is in Doze mode. However, as it is a method for setting Inexact alarms, the timing may or may not be exact.

On Android 12 (API level 31) or higher, inexact alarms are fired within one hour of the specified time, unless battery-saving restrictions are in place.

  1. setWindow()
public void setWindow(int type, 
            long windowStartMillis, 
            long windowLengthMillis, 
            PendingIntent operation)

This method can be used to set alarms that need to fire off after a certain duration when the specified time has passed. Here, windowStartMillis defines the earliest time at which the alarm can go off, and windowLengthMillis defines the duration in which the alarm should fire after windowStartMillis.

Methods to set Exact Alarms

Before diving into the available methods, we first need to discuss the permissions that need to be added to the AndroidManifest.xml file for setting exact alarms. We need to mention either of these two permissions in the Manifest file:

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />

The SCHEDULE_EXACT_ALARM permission needs to be granted by the user and can be revoked by the user and the system too. Whereas the USE_EXACT_ALARM permission is granted by the system itself to the app, so there is no hassle of asking the user to grant the permission.

So why is there a SCHEDULE_EXACT_ALARM permission in the first place?

The answer is that if your app uses USE_EXACT_ALARM permission, then it needs to present a very strong use case for the same while it is being evaluated for review/update on the Play Store. If the app cannot function without this permission, only then will the Play Store pass the app. Examples of such apps can be Calendar apps or Alarm apps.

  1. setExact()
public void setExact(int type, 
            long triggerAtMillis, 
            PendingIntent operation)

This method sets an alarm that fires at a 'nearly precise' time, as per the official documentation. To understand what 'nearly precise' means, I set up various alarms that, when fired, will send a notification in the Notification tray of the device.
The notification arrived at the specified time, and there was no delay. So, by 'nearly precise', the Android folks could mean that the alarm set via this method will be delivered with utmost precision. However, if the device is in Doze mode, the alarm will not go off at the specified time, but as the precision is guaranteed, it should be delivered in the nearest forthcoming maintenance window.

  1. setExactAndAllowWhileIdle()
public void setExactAndAllowWhileIdle(int type, 
            long triggerAtMillis, 
            PendingIntent operation)

Unlike setExact(), the alarms set via this method would go off even when the device is in Doze mode. When the specified time comes, the app is put into the system's temporary power exemption list for 10 seconds so that the app can take the required measures to complete the stated work.
In the official documentation, the words 'nearly precise' are used to convey the precision of firing the alarms set via this method. This method is considered battery-consuming and should be used only if no other option is available.

  1. setAlarmClock()
public void setAlarmClock(AlarmManager.AlarmClockInfo info, PendingIntent operation)

An alarm set via this method is guaranteed to fire off at the precise time, irrespective of whether the device is in Doze mode. As the name suggests, it's like setting an alarm clock and is the ultimate weapon for getting accurate results from the AlarmManager API.
But as Uncle Ben said to Peter Parker:

"With great power comes great responsibility."

This method should only be used carefully as these types of alarms can be extremely expensive in terms of battery use.

Setting Repeating Alarms

There are also methods to set repeating alarms. For setting an Inexact repeating alarm, one can use setInexactRepeating(), and for setting an exact repeating alarm, setRepeating() can be used.

public void setInexactRepeating(int type, 
            long triggerAtMillis, 
            long intervalMillis, 
            PendingIntent operation)
public void setRepeating(int type, 
            long triggerAtMillis, 
            long intervalMillis, 
            PendingIntent operation)

triggerAtMillis is the time in milliseconds when the alarm should first fire off, intervalMillis is the interval in milliseconds after which the alarm should repeat, and operation is the PendingIntent that will fire each time the alarm goes off.

Differences between the two methods

  1. If setInexactRepeating() is used, then the interval between the repetitions may vary, whereas with setRepeating(), the interval between alarms has almost no variation.

  2. With setInexactRepeating(), you can only provide one of the predefined interval constants, such as INTERVAL_FIFTEEN_MINUTES or INTERVAL_DAY, whereas with setRepeating(), a custom interval can be provided based on the use case.

From Android 4.4 (API Level 19) onward, all repeating alarms are considered Inexact, so it doesn't matter which method you use; the repeating alarms won't fire if the device is in Doze mode. Hence, rather than setting repeating alarms, it's better to set a single alarm, and when that alarm fires, set another one.

So yeah, AlarmManager is a tough nut, but that's why it's worth cracking! ๐Ÿ˜„

If you learned something, please like and share the article.
Connect with me on Github, LinkedIn and Twitter for more Android-related content ๐Ÿ™Œ

ย