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.
This chapter requires you to have read the core chapters of the book, in particular the chapter on services.
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.
There are a variety of things you will be able to configure about your
scheduled alarms with AlarmManager
.
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.
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.
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.
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.
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
:
onActivityResult()
of an existing activity (created via createPendingResult()
)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!");
}
}
}
In onCreate()
, in addition to setting up the “hello, world”-ish UI, we:
AlarmManager
, by calling getSystemService()
,
asking for the ALARM_SERVICE
, and casting the result to be an AlarmManager
PendingIntent
by calling createPendingResult()
, supplying
an empty Intent
as our “result” (since we do not really need it here)setRepeating()
on AlarmManager
The call to setRepeating()
is a bit complex, taking four parameters:
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 modePERIOD
) added to “now” as determined
by SystemClock.elapsedRealtime()
(the number of milliseconds since the
device was last rebooted)PendingIntent
to invoke for each of these eventsWhen 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:
Intent
inside the PendingIntent
matches the scheduled alarm’s
Intent
, in terms of component, action, data (Uri
), MIME type, and
categoriesPendingIntent
(here, ALARM_ID
) must also matchRunning 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.
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
:
INTERVAL_FIFTEEN_MINUTES
INTERVAL_HALF_HOUR
INTERVAL_HOUR
INTERVAL_HALF_DAY
INTERVAL_DAY
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.
In the above sample, we used ELAPSED_REALTIME
as the type of alarm. There
are three others:
ELAPSED_REALTIME_WAKEUP
RTC
RTC_WAKEUP
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.
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 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).
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.
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).
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.
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:
AlarmManager
-triggered event happens to occur when the user happens
to have one of your activities in the foreground, so you do not freeze the
UI, orHowever, 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:
onReceive()
?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.
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>
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);
}
}
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!");
}
}
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();
}
}
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.
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" />
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.
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 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 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);
}
}
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:
ELAPSED_REALTIME
)PERIOD
milliseconds from now)String
representing some tag to be used for battery usage logging
purposesOnAlarmListener
implementation, which in this case happens to be
the activity itselfnull
, indicating that we want onAlarm()
of the OnAlarmListener
to be called on the main application threadThe 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.