Earlier in this chapter we learned that we can use a BroadcastReceiver
to handle alarms, and even do work in the background for up to 10 seconds, though only on devices running API level 11 or greater.
In the previous section, we saw that handling alarms directly with services is not a reliable solution for scheduling long-running work, since there is no guarantee that our Service
will start up before the device returns to sleep.
We have a problem! If we want to perform long-running work in response to alarms, we need a solution that overcomes these limitations.
What we really want is to start a Service
to handle the work in the background, and to keep the device awake until the Service
has finished its work. Fortunately, we can do that by combining the wakefulness guarantees of BroadcastReceiver
to get the Service
started, then keep the device awake with explicit power management using PowerManager
and WakeLock
.
As you might guess, WakeLock
is a way to force the device to stay awake. WakeLocks come in various flavors, allowing apps to keep the screen on at varying brightness levels or just to keep the CPU powered up in order to do background work. To use WakeLocks, our application must request an additional permission in the manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
To keep the CPU powered up while we do background work in a Service
, we only need a partial WakeLock
, which won't keep the screen on, and which we can request from the PowerManager
like this:
PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE); WakeLock lock = pm.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, "my_app"); lock.acquire();
We'll need to acquire a WakeLock during our BroadcastReceiver's onReceive
method, and find some way to hand it to our Service
so that the Service
can release the lock once its work is done.
Unfortunately, WakeLocks are not parcelable, so we can't just send them to the Service
in an Intent
. The simplest solution is to manage the WakeLock
instance as a static property that both the BroadcastReceiver
and the target Service
can reach.
This is not difficult to implement, but we don't actually need to implement it ourselves—we can use the handy v4 support library class, WakefulBroadcastReceiver
.
WakefulBroadcastReceiver
exposes two static methods that take care of acquiring and releasing a partial WakeLock
. We can acquire the WakeLock
, and start the Service
with a single call to startWakefulService
:
ComponentName startWakefulService(Context context, Intent intent);
And when our Service
has finished its work, it can release the WakeLock
with the corresponding call to completeWakefulIntent
:
boolean completeWakefulIntent(Intent intent);
Since these methods are static, we don't need to extend WakefulBroadcastReceiver
to use them. Our BroadcastReceiver
just needs to start the Service
using startWakefulService
:
class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent serviceIntent = new Intent( context, MyIntentService.class); WakefulBroadcastReceiver.startWakefulService( context, serviceIntent); } }
We must make sure to release the WakeLock
once the Service
has finished its work, otherwise we'll drain the battery by keeping the CPU powered up unnecessarily:
class MyIntentService extends IntentService { @Override protected final void onHandleIntent(Intent intent) { try { // do background work while the CPU is kept awake } finally { WakefulBroadcastReceiver. completeWakefulIntent(intent); } } }
This is great—by using a statically registered BroadcastReceiver
we've ensured that we receive the alarm, even if our application is not running when the alarm comes due. When we receive the alarm we acquire a WakeLock
, keeping the device awake while our Service
starts up and does its potentially long-running work. Once our work is done, we release the WakeLock
to allow the device to sleep again and conserve power.
18.227.10.213