AlarmManager and the Scheduled Service Pattern

Many applications have the need to get control every so often to do a bit of work. And, many times, those applications need to get control in the background, regardless of what the user may be doing (or not doing) at the time.

The solution, in some cases, is to use AlarmManager, which is roughly akin to cron on Linux and macOS and Scheduled Tasks in Windows. You teach AlarmManager when you want to get control back, and AlarmManager will give you control at that time.

Android 5.0 added a separate JobScheduler. Like AlarmManager, JobScheduler is designed for background work. JobScheduler is more sophisticated than is AlarmManager. For example, if you need an Internet connection to do your work, JobScheduler will only give you control if there is an Internet connection. If your app’s minSdkVersion is 21 or higher, you might consider using JobScheduler instead of AlarmManager. JobScheduler is covered in an upcoming chapter.

Prerequisites

This chapter requires you to have read the core chapters of the book, in particular the chapter on services.

Scenarios

The two main axes to consider with scheduled work are frequency and foreground (vs. background).

If you have an activity that needs to get control every second, the simplest approach is to use a postDelayed() loop, scheduling a Runnable to be invoked after a certain delay, where the Runnable reschedules itself to be invoked after the delay in addition to doing some work. We saw this in the chapter on threads. This has the advantages of giving you control back on the main application thread and avoiding the need for any background threads.

On the far other end of the spectrum, you may need to get control on a somewhat slower frequency (e.g., every 15 minutes), and do so in the background, even if nothing of your app is presently running. You might need to poll some Web server for new information, such as downloading updates to an RSS feed. This is the scenario that AlarmManager excels at. While postDelayed() works inside your process (and therefore does not work if you no longer have a process), AlarmManager maintains its schedule outside of your process. Hence, it can arrange to give you control, even if it has to start up a new process for you along the way.

Options

There are a variety of things you will be able to configure about your scheduled alarms with AlarmManager.

Wake Up… Or Not?

The biggest one is whether or not the scheduled event should wake up the device.

A device goes into a sleep mode shortly after the screen goes dark. During this time, nothing at the application layer will run, until something wakes up the device. Waking up the device does not necessarily turn on the screen — it may just be that the CPU starts running your process again.

If you choose a “wakeup”-style alarm, Android will wake up the device to give you control. This would be appropriate if you need this work to occur even if the user is not actively using the device, such as your app checking for critical email messages in the middle of the night. However, it does drain the battery some.

Alternatively, you can choose an alarm that will not wake up the device. If your desired time arrives and the device is asleep, you will not get control until something else wakes up the device.

Repeating… Or Not?

You can create a “one-shot” alarm, to get control once at a particular time in the future. Or, you can create an alarm that will give you control periodically, at a fixed period of your choice (e.g., every 15 minutes).

If you need to get control at multiple times, but the schedule is irregular, use a “one-shot” alarm for the nearest time, where you do your work and schedule a “one-shot” alarm for the next-nearest time. This would be appropriate for scenarios like a calendar application, where you need to let the user know about upcoming appointments, but the times for those appointments may not have any fixed schedule.

However, for most polling operations (e.g., checking for new messages every NN minutes), a repeating alarm will typically be the better answer.

Inexact… Or Not?

If you do choose a repeating alarm, you will have your choice over having (relatively) precise control over the timing of event or not.

If you choose an “inexact” alarm, while you will provide Android with a suggested time for the first event and a period for subsequent events, Android reserves the right to shift your schedule somewhat, so it can process your events and others around the same time. This is particularly important for “wakeup”-style alarms, as it is more power-efficient to wake up the device fewer times, so Android will try to combine multiple apps’ events to be around the same time to minimize the frequency of waking up the device.

However, inexact alarms are annoying to test and debug, simply because you do not have control over when they will be invoked. Hence, during development, you might start with an exact alarm, then switch to inexact alarms once most of your business logic is debugged.

Note that Android 4.4 changes the behavior of AlarmManager, such that it is more difficult to actually create an exact-repeating alarm schedule. This will be examined in greater detail shortly, as we review the various methods and flags for scheduling AlarmManager events.

Absolute Time… Or Not?

As part of the alarm configuration, you will tell Android when the event is to occur (for one-shot alarms) or when the event is to first occur (for repeating alarms). You can provide that time in one of two ways:

For most polling operations, particularly for periods more frequent than once per day, specifying the time relative to now is easiest. However, some alarms may need to tie into “real world time”, such as alarm clocks and calendar alerts — for those, you will need to use the real-time clock (typically by means of a Java Calendar object) to indicate when the event should occur.

What Happens (Or Not???)

And, of course, you will need to tell Android what to do when each of these timer events occurs. You will do that in the form of supplying a PendingIntent. First mentioned in the chapter on services, a PendingIntent is a Parcelable object, one that indicates an operation to be performed upon an Intent:

A Simple Example

A trivial sample app using AlarmManager can be found in AlarmManager/Simple.

This application consists of a single activity, SimpleAlarmDemoActivity, that will both set up an alarm schedule and respond to alarms:

package com.commonsware.android.alarm;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.widget.Toast;

public class SimpleAlarmDemoActivity extends Activity {
  private static final int ALARM_ID=1337;
  private static final int PERIOD=5000;
  private PendingIntent pi=null;
  private AlarmManager mgr=null;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mgr=(AlarmManager)getSystemService(ALARM_SERVICE);
    pi=createPendingResult(ALARM_ID, new Intent(), 0);
    mgr.setRepeating(AlarmManager.ELAPSED_REALTIME,
                     SystemClock.elapsedRealtime() + PERIOD, PERIOD, pi);
  }

  @Override
  public void onDestroy() {
    mgr.cancel(pi);

    super.onDestroy();
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode,
                                  Intent data) {
    if (requestCode == ALARM_ID) {
      Toast.makeText(this, R.string.toast, Toast.LENGTH_SHORT).show();
      Log.d(getClass().getSimpleName(), "I ran!");
    }
  }
}
(from AlarmManager/Simple/app/src/main/java/com/commonsware/android/alarm/SimpleAlarmDemoActivity.java)

In onCreate(), in addition to setting up the “hello, world”-ish UI, we:

The call to setRepeating() is a bit complex, taking four parameters:

  1. The type of alarm we want, in this case ELAPSED_REALTIME, indicating that we want to use a relative time base for when the first event should occur (i.e., relative to now) and that we do not need to wake up the device out of any sleep mode
  2. The time when we want the first event to occur, in this case specified as a time delta in milliseconds (PERIOD) added to “now” as determined by SystemClock.elapsedRealtime() (the number of milliseconds since the device was last rebooted)
  3. The number of milliseconds to occur between events
  4. The PendingIntent to invoke for each of these events

When the event occurs, since we used createPendingResult() to create the PendingIntent, our activity gets control in onActivityResult(), where we simply display a Toast (if the event is for our alarm’s request ID). This continues until the activity is destroyed (e.g., pressing the BACK button), at which time we cancel() the alarm, supplying a PendingIntent to indicate which alarm to cancel. While here we use the same PendingIntent object as we used for scheduling the alarm, that is not required — it merely has to be an equivalent PendingIntent, meaning:

Running this simply brings up a Toast every five seconds until you BACK out of the activity… except on Android 5.1 and higher, where it will appear every minute. That is due to a limitation of setRepeating() that we will see in the next section.

The Five set…() Varieties

There are five methods that you can call on AlarmManager to establish an alarm, including the setRepeating() demonstrated above.

On Android 4.4 (API Level 19) and higher, setExact() is used for a one-shot alarm, where you want to get control at one specific time in the future. This would be used for specific events or for irregular alarm schedules.

On Android 4.3 and below, and for apps whose targetSdkVersion is set to 18 or lower, set() has the same behavior as setExact(). However, on Android 4.4 and above, apps with their targetSdkVersion set to be 19 or higher will have different, inexact behavior for set(). The time of the event is considered a minimum — your PendingIntent will not be invoked before your desired time, but it can occur any time thereafter… and you do not have control over how long that delay will be. As with all “inexact” schedules, the objective is for Android to be able to “batch” these events, to do several around the same time, for greater efficiency, particularly when waking up the device.

On Android 4.4 and higher, you have a setWindow() option that is a bit of a hybrid between the new-style set() and setExact(). Here, you specify the time you want the event to occur and an amount of time that Android can “flex” the actual event. So, for example, you might set up an event to occur every hour, with a “window” of five minutes, to allow Android the flexibility to invoke your PendingIntent within that five-minute window. This allows for better battery optimization than with setExact(), while still giving you some control over how far “off the mark” the event can occur.

On Android 4.3 and below, and for apps whose targetSdkVersion is set to 18 or lower, setRepeating() is used for an alarm that should occur at specific points in time at a specific frequency. In addition to specifying the time of the first event, you also specify the period for future events. Android will endeavor to give you control at precisely those times, though since Android is not a real-time operating system (RTOS), microsecond-level accuracy is certainly not guaranteed. However, note that as of Android 5.1, your minimum period is one minute (60000ms) — values less than that will be rounded up to one minute. This minimum period is enforced regardless of your targetSdkVersion value.

setInexactRepeating() is used for an alarm that should occur on a general frequency, such as every 15 minutes. In addition to specifying the time of the first event, you also specify a general frequency, as one of the following public static data members on AlarmManager:

Android guarantees that it will give your app control somewhere during that time window, but precisely when within that window is up to Android.

Note that on Android 4.4 and above, for apps with their targetSdkVersion set to be 19 or higher, setRepeating() behaves identically to setInexactRepeating() – in other words, all repeating alarms are inexact. The only way to get exact repeating would be to use setExact() and to re-schedule the event yourself, rather than relying upon Android doing that for you automatically. Ideally, you use setInexactRepeating(), to help extend battery life.

And, note that on Android 5.1 and higher, alarms must be set to occur at least 5 seconds in the future from now. You cannot trigger an alarm to occur in the future sooner than 5 seconds.

The Four Types of Alarms

In the above sample, we used ELAPSED_REALTIME as the type of alarm. There are three others:

Those with _WAKEUP at the end will wake up a device out of sleep mode to execute the PendingIntent — otherwise, the alarm will wait until the device is awake for other means.

Those that begin with ELAPSED_REALTIME expect the second parameter to setRepeating() to be a timestamp based upon SystemClock.elapsedRealtime(). Those that begin with RTC, however, expect the second parameter to be based upon System.currentTimeMillis(), the classic Java “what is the current time in milliseconds since the Unix epoch” method.

When to Schedule Alarms

The sample, though, begs a bit of a question: when are we supposed to set up these alarms? The sample just does so in onCreate(), but is that sufficient?

For most apps, the answer is “no”. Here are the three times that you will need to ensure that your alarms get scheduled:

When User First Runs Your App

When your app is first installed, none of your alarms are set up, because your code has not yet run to schedule them. There is no means of setting up alarm information in the manifest or something that might automatically kick in.

Hence, you will need to schedule your alarms when the user first runs your app.

As a simplifying measure — and to cover another scenario outlined below — you might be able to simply get away with scheduling your alarms every time the user runs your app, as the sample app shown above does. This works for one-shot alarms (using set()) and for alarms with short polling periods, and it works because setting up a new alarm schedule for an equivalent PendingIntent will replace the old schedule. However, for repeating alarms with slower polling periods, it may excessively delay your events. For example, suppose you have an alarm set to go off every 24 hours, and the user happens to run your app 5 minutes before the next event was to occur — if you blindly reschedule the alarm, instead of going off in 5 minutes, it might not go off for another 24 hours.

There are more sophisticated approaches for this (e.g., using a SharedPreferences value to determine if your app has run before or not).

On Boot

The alarm schedule for alarm manager is wiped clean on a reboot, unlike cron or Windows Scheduled Tasks. Hence, you will need to get control at boot time to re-establish your alarms, if you want them to start up again after a reboot. We saw how to get control at boot time, via an ACTION_BOOT_COMPLETED BroadcastReceiver, back in the chapter on broadcasts.

After a Force-Stop

There are other events that could cause your alarms to become unscheduled. The best example of this is if the user goes into the Settings app and presses “Force Stop” for your app. At this point, on Android 3.1+, nothing of your code will run again, until the user manually launches some activity of yours.

If you are rescheduling your alarms every time your app runs, this will be corrected the next time the user launches your app. And, by definition, you cannot do anything until the user runs one of your activities, anyway.

If you are trying to avoid rescheduling your alarms on each run, though, you have a couple of options.

One is to record the time when your alarm-triggered events occur, each time they occur, such as by updating a SharedPreference. When the user launches one of your activities, you check the last-event time — if it was too long ago (e.g., well over your polling period), you assume that the alarm had been canceled, and you reschedule it.

Another is to rely on FLAG_NO_CREATE. You can pass this as a parameter to any of the PendingIntent factory methods, to indicate that Android should only return an existing PendingIntent if there is one, and not create one if there is not:


PendingIntent pi=PendingIntent.getBroadcast(ctxt, 0, i, PendingIntent.FLAG_NO_CREATE);

If the PendingIntent is null, your alarm has been canceled — otherwise, Android would already have such a PendingIntent and would have returned it to you. This feels a bit like a side-effect, so we cannot rule out the possibility that, in future versions of Android, this technique could result in false positives (null PendingIntent despite the scheduled alarm) or false negatives (non-null PendingIntent despite a canceled alarm).

Archetype: Scheduled Service Polling

The classic AlarmManager scenario is where you want to do a chunk of work, in the background, on a periodic basis. This is fairly simple to set up in Android, though perhaps not quite as simple as you might think.

Back to the Main Application Thread

When an AlarmManager-triggered event occurs, it is very likely that your application is not running. This means that the PendingIntent is going to have to start up your process to have you do some work. Since everything that a PendingIntent can do intrinsically gives you control on your main application thread, you are going to have to determine how you want to move your work to a background thread.

One approach is to use a PendingIntent created by getService(), and have it send a command to an IntentService that you write. Since IntentService does its work on a background thread, you can take whatever time you need, without interfering with the behavior of the main application thread. This is particularly important when:

Problem: Keeping the Device Awake

However, this approach has a flaw: the device might fall asleep before our service can complete its work, if we woke it up out of sleep mode to process the event.

For a _WAKEUP-style alarm, Android makes precisely one guarantee: if the PendingIntent supplied to AlarmManager for the alarm is one created by getBroadcast() to send a broadcast Intent, Android will ensure that the device will stay awake long enough for onReceive() to be completed. Anything beyond that is not guaranteed.

While sending the command directly to the service via a getService() PendingIntent is straightforward, Android makes no guarantees about what happens after AlarmManager wakes up the device, and the device could fall back asleep before our IntentService completes processing of onHandleIntent().

So, we have two problems:

  1. How do we ensure that the service itself keeps the device awake while it is doing its work?
  2. How do we ensure that we can actually get that service started, without the device falling asleep after we return from onReceive()?

Return of the JobIntentService

JobIntentService neatly solves both of those problems. It takes responsibility for keeping the device awake, both to set up the work, and to process the work.

So, what we need to do is put our business logic in a JobIntentService, then use a broadcast PendingIntent to trigger enqueuing work to that JobIntentService. JobIntentService will take it from there.

Examining a Sample

An incrementally-less-trivial sample app using AlarmManager for the scheduled service pattern can be found in AlarmManager/Scheduled.

This application consists of three components: a BroadcastReceiver, a JobIntentService, and an Activity.

This sample demonstrates scheduling your alarms at two points in your app:

For the boot-time scenario, we need a BroadcastReceiver set up to receive the ACTION_BOOT_COMPLETED broadcast, with the appropriate permission. So, we set that up, along with our other components, in the manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.commonsware.android.wakesvc"
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:versionCode="1"
  android:versionName="1.0">

  <uses-permission android:name="android.permission.WAKE_LOCK" />
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name">
    <activity
      android:name="ScheduledServiceDemoActivity"
      android:label="@string/app_name"
      android:theme="@android:style/Theme.Translucent.NoTitleBar">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>

    <receiver android:name="PollReceiver">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
      </intent-filter>
    </receiver>

    <service
      android:name="ScheduledService"
      android:permission="android.permission.BIND_JOB_SERVICE" />
  </application>

</manifest>
(from AlarmManager/Scheduled/app/src/main/AndroidManifest.xml)

The PollReceiver has its onReceive() method, to be called at boot time, which delegates its work to a scheduleAlarms() static method, so that logic can also be used by our activity:

package com.commonsware.android.wakesvc;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;

public class PollReceiver extends BroadcastReceiver {
  private static final int PERIOD=900000; // 15 minutes
  private static final int INITIAL_DELAY=5000; // 5 seconds

  @Override
  public void onReceive(Context ctxt, Intent i) {
    if (i.getAction() == null) {
      ScheduledService.enqueueWork(ctxt);
    }
    else {
      scheduleAlarms(ctxt);
    }
  }

  static void scheduleAlarms(Context ctxt) {
    AlarmManager mgr=
        (AlarmManager)ctxt.getSystemService(Context.ALARM_SERVICE);
    Intent i=new Intent(ctxt, PollReceiver.class);
    PendingIntent pi=PendingIntent.getBroadcast(ctxt, 0, i, 0);

    mgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + INITIAL_DELAY,
                     PERIOD, pi);

  }
}
(from AlarmManager/Scheduled/app/src/main/java/com/commonsware/android/wakesvc/PollReceiver.java)

The scheduleAlarms() method retrieves our AlarmManager, creates a PendingIntent designed to call startService() on our ScheduledService, and schedules an exact repeating alarm to have that command be sent every five seconds.

The ScheduledService itself is the epitome of “trivial”, simply logging a message to Logcat on each command:

package com.commonsware.android.wakesvc;

import android.content.Context;
import android.content.Intent;
import android.support.v4.app.JobIntentService;
import android.util.Log;

public class ScheduledService extends JobIntentService {
  private static final int UNIQUE_JOB_ID=1337;

  static void enqueueWork(Context ctxt) {
    enqueueWork(ctxt, ScheduledService.class, UNIQUE_JOB_ID,
      new Intent(ctxt, ScheduledService.class));
  }

  @Override
  public void onHandleWork(Intent i) {
    Log.d(getClass().getSimpleName(), "I ran!");
  }
}
(from AlarmManager/Scheduled/app/src/main/java/com/commonsware/android/wakesvc/ScheduledService.java)

That being said, because this is an JobIntentService, we could do much more in onHandleWork() and not worry about tying up the main application thread.

Our activity — ScheduledServiceDemoActivity — is set up with Theme.Translucent.NoTitleBar in the manifest, never calls setContentView(), and calls finish() right from onCreate(). As a result, it has no UI. It simply calls scheduleAlarms() and raises a Toast to indicate that the alarms are indeed scheduled:

package com.commonsware.android.wakesvc;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

public class ScheduledServiceDemoActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    PollReceiver.scheduleAlarms(this);

    Toast.makeText(this, R.string.alarms_scheduled, Toast.LENGTH_LONG)
         .show();
    finish();
  }
}
(from AlarmManager/Scheduled/app/src/main/java/com/commonsware/android/wakesvc/ScheduledServiceDemoActivity.java)

On Android 3.1+, we also need this activity to move our application out of the stopped state and allow that boot-time BroadcastReceiver to work.

If you run this app on a device or emulator, after seeing the initial Toast, messages will appear in Logcat every 15 minutes, even though you have no activity running.

How the Magic Works

A JobIntentService keeps the device awake by using a WakeLock. A WakeLock allows a “userland” (e.g., Android SDK) app to tell the Linux kernel at the heart of Android to keep the device awake, with the CPU powered on, indefinitely, until the WakeLock is released.

This can be a wee bit dangerous, as you can accidentally keep the device awake much longer than you need to. That is why using existing code like JobIntentService can be useful — to use more-tested code rather than rolling your own.

Also note that to request a WakeLock — from your code or code from a library – you need to request the WAKE_LOCK permission, as we saw in the manifest:

  <uses-permission android:name="android.permission.WAKE_LOCK" />
(from AlarmManager/Scheduled/app/src/main/AndroidManifest.xml)

Warning: Not All Android Devices Play Nice

Some Android devices take liberties with the way AlarmManager works, in ways that may affect your applications.

One example of this today is the SONY Xperia Z. It has a “STAMINA mode” that the user can toggle on via the “Power Management” screen in Settings. This mode will be entered when the device’s screen turns off, if the device is not plugged in and charging. The user can add apps to a whitelist (“Apps active in standby”), where STAMINA mode does not affect those apps’ behavior.

_WAKEUP style alarms do not wake up the device when it is in STAMINA mode. The behavior is a bit reminiscent of non-_WAKEUP alarms. Alarms that occur while the device is asleep are suppressed, and you get one invocation of your PendingIntent at the point the device wakes back up. At that point, the schedule continues as though the alarms had been going off all along. Apps on the whitelist are unaffected.

Mostly, you need to be aware of this from a support standpoint. If Xperia Z owners complain that your app behaves oddly, and you determine that your alarms are not going off, see if they have STAMINA mode on, and if they do, ask them to add your app to the whitelist.

If you are using “if my alarm has not gone off in X amount of time, the user perhaps force-stopped me, so let me reschedule my alarms” logic, you should be OK. Before one of your activities gets a chance to make that check, your post-wakeup alarm should have been invoked, so you can update your event log and last-run timestamp. Hence, you should not be tripped up by STAMINA and accidentally reschedule your alarms (potentially causing duplicates, depending upon your alarm-scheduling logic).

Other devices with similar characteristics include Sony’s Xperia P, Xperia U, Xperia sola, and Xperia go.

Debugging Alarms

If you are encountering issues with your alarms, the first thing to do is to ensure that the alarm schedule in AlarmManager is what you expect it to be. To do that, run adb shell dumpsys alarm from a command prompt. This will dump a report of all the scheduled alarms, including when they are set to be invoked next (with portions replaced by vertical ellipses to keep this listing from being too long):


Current Alarm Manager state:
 
  Realtime wakeup (now=2013-03-09 07:49:51):
  RTC_WAKEUP #11: Alarm{429c6028 type 0 com.android.providers.calendar}
    type=0 when=+21h40m9s528ms repeatInterval=0 count=0
    operation=PendingIntent{42ec2f40: PendingIntentRecord{434fb2f8 com.android.providers.calendar broadcastIntent}}
  RTC_WAKEUP #10: Alarm{42e17e28 type 0 com.google.android.gms}
    type=0 when=+18h10m8s480ms repeatInterval=86400000 count=1
    operation=PendingIntent{42e15d20: PendingIntentRecord{42e0cc28 com.google.android.gms startService}}
.
.
.
 
  Elapsed realtime wakeup (now=+6d15h50m2s672ms):
  ELAPSED_WAKEUP #16: Alarm{42cf26f0 type 2 com.google.android.apps.maps}
    type=2 when=+999d23h59m59s999ms repeatInterval=0 count=0
    operation=PendingIntent{42de2dc0: PendingIntentRecord{42ac73e8 com.google.android.apps.maps broadcastIntent}}
  ELAPSED_WAKEUP #15: Alarm{42c4a638 type 2 com.google.android.apps.maps}
    type=2 when=+1d18h10m8s894ms repeatInterval=0 count=0
    operation=PendingIntent{42ab50c8: PendingIntentRecord{42e2c020 com.google.android.apps.maps broadcastIntent}}
.
.
.

  Broadcast ref count: 0

  Top Alarms:
    +14m24s97ms running, 0 wakeups, 9567 alarms: android
       act=android.intent.action.TIME_TICK
    +1m15s72ms running, 4890 wakeups, 4890 alarms: com.android.phone
       act=com.android.server.sip.SipWakeupTimer@42626830
    +1m13s465ms running, 0 wakeups, 320 alarms: android
       act=com.android.server.action.NETWORK_STATS_POLL
    +45s803ms running, 0 wakeups, 639 alarms: com.google.android.deskclock
       act=com.android.deskclock.ON_QUARTER_HOUR
    +42s830ms running, 0 wakeups, 19 alarms: com.android.phone
       act=com.android.phone.UPDATE_CALLER_INFO_CACHE cmp={com.android.phone/com.android.phone.CallerInfoCacheUpdateReceiver}
    +35s479ms running, 0 wakeups, 954 alarms: android
       act=com.android.server.ThrottleManager.action.POLL
    +14s28ms running, 1609 wakeups, 1609 alarms: com.android.phone
       act=com.android.internal.telephony.gprs-data-stall
    +11s98ms running, 171 wakeups, 171 alarms: com.android.providers.calendar
       act=com.android.providers.calendar.intent.CalendarProvider2
    +8s380ms running, 893 wakeups, 893 alarms: android
       act=android.content.syncmanager.SYNC_ALARM
    +8s353ms running, 569 wakeups, 569 alarms: com.google.android.apps.maps
       cmp={com.google.android.apps.maps/com.google.googlenav.prefetch.android.PrefetcherService}
 
  Alarm Stats:
  com.google.android.location +120ms running, 12 wakeups:
    +73ms 7 wakes 7 alarms: act=com.google.android.location.nlp.ALARM_WAKEUP_CACHE_UPDATER
    +47ms 5 wakes 5 alarms: act=com.google.android.location.nlp.ALARM_WAKEUP_LOCATOR
  android +15m32s920ms running, 1347 wakeups:
    +14m24s97ms 0 wakes 9567 alarms: act=android.intent.action.TIME_TICK
    +1m13s465ms 0 wakes 320 alarms: act=com.android.server.action.NETWORK_STATS_POLL
    +35s479ms 0 wakes 954 alarms: act=com.android.server.ThrottleManager.action.POLL
    +8s380ms 893 wakes 893 alarms: act=android.content.syncmanager.SYNC_ALARM
    +7s734ms 159 wakes 159 alarms: act=android.appwidget.action.APPWIDGET_UPDATE cmp={com.guywmustang.silentwidget/com.guywmustang.silentwidgetlib.SilentWidgetProvider}
    +1s144ms 151 wakes 151 alarms: act=android.app.backup.intent.RUN
    +922ms 0 wakes 6 alarms: act=android.intent.action.DATE_CHANGED
    +479ms 66 wakes 66 alarms: act=com.android.server.WifiManager.action.DEVICE_IDLE
    +383ms 56 wakes 56 alarms: act=com.android.server.WifiManager.action.DELAYED_DRIVER_STOP
    +101ms 14 wakes 14 alarms: act=com.android.server.action.UPDATE_TWILIGHT_STATE
    +100ms 7 wakes 7 alarms: act=com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD
    +9ms 1 wakes 1 alarms: act=android.net.wifi.DHCP_RENEW
    +3ms 0 wakes 1 alarms: act=com.android.server.NetworkTimeUpdateService.action.POLL
  com.google.android.apps.maps +14s742ms running, 911 wakeups:
    +8s353ms 569 wakes 569 alarms: cmp={com.google.android.apps.maps/com.google.googlenav.prefetch.android.PrefetcherService}
    +2s211ms 85 wakes 85 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_LOCATOR
    +1s206ms 103 wakes 103 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_SENSOR_UPLOADER
    +807ms 2 wakes 2 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_BURST_COLLECTION_TRIGGER
    +759ms 56 wakes 56 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_S_COLLECTOR
    +566ms 10 wakes 10 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_CACHE_UPDATER
    +385ms 39 wakes 39 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_IN_OUT_DOOR_COLLECTOR
    +308ms 31 wakes 31 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_ACTIVE_COLLECTOR
    +77ms 8 wakes 8 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_ACTIVITY_DETECTION
    +42ms 4 wakes 4 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_PASSIVE_COLLECTOR
    +28ms 4 wakes 4 alarms: act=com.google.android.apps.maps.nlp.ALARM_WAKEUP_CALIBRATION_COLLECTOR
.
.
.

You are given details of each outstanding alarm, including the all-important when value indicating the time the alarm should be invoked next, if it is not canceled first (e.g., when=+5d15h10m7s782ms), along with the package requesting the alarm. You can use this to identify your app’s alarms and see when they should be invoked next.

You are also given:

Note, though, that for inexact alarms, the when value may not indicate when the event will actually occur.

Android 6.0 and the War on Background Processing

Android 6.0 introduced some changes to the behavior of AlarmManager that significantly affect its use on Android 6.0+ devices. These changes also affect JobScheduler, and so this topic is covered in grand detail at the end of the JobScheduler chapter.

Android 7.0 and OnAlarmListener

Android 7.0 introduced a curious variant of the existing set(), setExact(), and setWindow() methods. Rather than taking a PendingIntent, they take an implementation of OnAlarmListener. That listener’s onAlarm() method then gets called when the alarm is scheduled to go off.

These methods are only useful if the app has a process running and that process is likely to be running when the time for the alarm is to occur. If the app’s process is terminated after the OnAlarmListener is registered, the alarms are canceled, as the OnAlarmListener no longer exists.

For RTC and ELAPSED_REALTIME alarms, it is unclear what value there is in these AlarmManager methods over using some other in-process timing mechanism, such as Java’s ScheduledExecutorService.

However, for RTC_WAKEUP and ELAPSED_REALTIME_WAKEUP alarms, the new OnAlarmListener methods may be useful, if you expect the device to be asleep but the process still running, and you want to get control to go do something. However, they still only make sense if you only want to get control if you already have a process running, and if your process goes away you do not mind the alarms going away.

There may be a set of apps that could use this. The author cannot quite figure out what such an app would be.

To illustrate the use of OnAlarmListener, we can turn to the AlarmManager/Listener sample app. This is reminiscent of the AlarmManager/Simple app shown in the beginning of this chapter, where we want an activity to get control every five seconds to show a Toast However, in this case, rather than use setRepeating() and createPendingResult(), we will use setWindow() and OnAlarmListener:

package com.commonsware.android.alarm;

import android.app.Activity;
import android.app.AlarmManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.widget.Toast;

public class SimpleAlarmDemoActivity extends Activity
  implements AlarmManager.OnAlarmListener {
  private static final int PERIOD=5000;
  private static final int WINDOW=10000;
  private AlarmManager mgr=null;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mgr=getSystemService(AlarmManager.class);
    schedule();
  }

  @Override
  public void onDestroy() {
    mgr.cancel(this);

    super.onDestroy();
  }

  @Override
  public void onAlarm() {
    Toast.makeText(this, R.string.toast, Toast.LENGTH_SHORT).show();
    Log.d(getClass().getSimpleName(), "I ran!");
    schedule();
  }

  private void schedule() {
    mgr.setWindow(AlarmManager.ELAPSED_REALTIME,
      SystemClock.elapsedRealtime()+PERIOD, WINDOW,
      getClass().getSimpleName(), this, null);
  }
}
(from AlarmManager/Listener/app/src/main/java/com/commonsware/android/alarm/SimpleAlarmDemoActivity.java)

onCreate() gets an AlarmManager as before. However, since this version of the app has a minSdkVersion of 24, we can use the version of getSystemService() that takes the desired system service Class object as a parameter and returns the system service instance in the proper data type.

Then, onCreate() calls schedule(), which in turn calls setWindow(). The version of setWindow() that we are using takes:

The alternative to null for the latter parameter would be a Handler from a HandlerThread, indicating that onAlarm() should be called on that thread.

Eventually, onAlarm() is called, where we show a Toast, log a message to Logcat,… and call schedule() again, so our alarm repeats.

Later, when the activity is destroyed, we call cancel(), passing in our OnAlarmListener, so all alarms tied to that listener will be discontinued.