If we are running a task and it needs to complete as soon as possible, we can prevent the device's CPU from going to sleep. This allows the task to finish without interruption.
To prevent the CPU from going to sleep, we can use a wake lock:
WakefulBroadcastReceiver
type.[assembly: UsesPermission(Manifest.Permission.WakeLock)]
WakefulBroadcastReceiver
and invoke the StartWakefulService()
method:[BroadcastReceiver] public class CriticalReceiver : WakefulBroadcastReceiver { public override void OnReceive( Context context, Intent intent) { var serviceIntent = new Intent( context, typeof(CriticalService)); StartWakefulService(context, serviceIntent); } }
CompleteWakefulIntent()
method:[Service] public class CriticalService : IntentService { protected override void OnHandleIntent (Intent intent) { try { // perform the task } finally { WakefulBroadcastReceiver.CompleteWakefulIntent( intent); } } };
SendBroadcast()
method on the activity:SendBroadcast(new Intent(this, typeof(CriticalReceiver)));
PowerManager
instance and requesting a lock with the NewWakeLock()
method:var manager = PowerManager.FromContext(this); var wakeLock = manager.NewWakeLock( WakeLockFlags.Partial, "WakeLockTag"); wakeLock.Acquire(); try { // perform the task } finally { wakeLock.Release(); }
Some tasks are critical and need to be finished as soon as possible and as fast as possible. Normally, if the device is left idle, the screen and then the CPU turn off. Turning off various pieces of hardware is a great way to preserve battery life, but it also prevents some important tasks from completing until the CPU is awoken by the user.
If we have a task that is important to finish in a timely fashion, we can prevent the CPU from turning off by obtaining a wake lock. This is done by either using the recommended way of a WakefulBroadcastReceiver
instance or by manually acquiring a wake lock from the PowerManager
instance.
Using a WakefulBroadcastReceiver
instance is very useful when the task comes from push notifications, as this will wake the CPU so that the message can be handled before going back to sleep. We just have to add the Xamarin Support Library v4 NuGet or component into our project, and then instead of inheriting from BroadcastReceiver
, we inherit from the WakefulBroadcastReceiver
type. This will automatically acquire a wake lock and we just have to implement the task we want to perform.
As the WakefulBroadcastReceiver
instance is just an extended BroadcastReceiver
instance, we cannot execute long-running or asynchronous tasks. If we wish to perform a task, we start that task in a service using the static StartWakefulService()
method. We pass an Intent
instance to this method, which specifies the service to start. Once the service has completed, we invoke the static CompleteWakefulIntent()
method on the WakefulBroadcastReceiver
type.
It is important to release the wake lock and to do it as soon as possible to avoid battery drain. In the case of exceptions, wrapping the execution of the task in a try
/finally
block will ensure that even if a problem occurs, the wake lock is released. This is important because even if the user presses the power button when a wake lock is being held, the CPU will not go to sleep.
If the task does not need to be in a broadcast receiver or a service, we can acquire a wake lock directly from the PowerManager
instance. This is simple to implement by first obtaining the PowerManager
instance through the FromContext()
method. Then, we request a new wake lock using the NewWakeLock()
method. This method takes two parameters, the wake lock type and the tag that is used in debugging.
There are several types of wake locks, some of which keep the screen on. If we want to keep just the CPU awake, we use the Partial
value of the WakeLockFlag
enumeration.
Once we have the wake lock, we have to acquire the actual lock by invoking the Acquire()
method. After invoking this method, the device's CPU is guaranteed not to turn off until we invoke the Release()
method. As the wake is not released until explicitly instructed, it is best to wrap the task in a try
/finally
block to ensure that the wake lock will always be released.
Some critical services don't need to run continuously, but rather once every few minutes. We can do this by setting up an alarm, and in this case, a repeating alarm, to broadcast an intent for our broadcast receiver:
var intent = new Intent(this, typeof(CriticalReceiver)); intent.PutExtra(CriticalService.DataKey, "Using alarm manager."); var pending = PendingIntent.GetBroadcast(ApplicationContext, 123, intent, 0); var manager = AlarmManager.FromContext(this); manager.SetRepeating(AlarmType.RtcWakeup, 0, 30*1000, pending);
When the alarm triggers, it will wake the device and broadcast the intent. The broadcast receiver will then acquire a wake lock and start the service.