Need wake lock in IntentService : why go via WakefulBroadcastReceiver? - android

I have an IntentService which is called from elsewhere (say from component X) in my app. I want its onHandleIntent to run with a wake lock. There seem to be two ways of doing this:
Acquire and release a wake lock in onHandleIntent.
Create a new WakefulBroadcastReceiver which starts this service. In component X, call this receiver instead of the service directly.
The second option seems to be recommended. But why? Does the added indirection and boilerplate offer any advantage over the first approach?

Back in 2010, we were told that the only guarantee with AlarmManager and _WAKEUP-style alarms was that if we used a broadcast PendingIntent, then Android would keep the device awake long enough for onReceive() to complete. Any other type of PendingIntent did not have that sort of guarantee.
However, onReceive() of a BroadcastReceiver is called on the main application thread, and we cannot safely spend much time there. Ideally, it's sub-millisecond, as for all you know, your UI happens to be in the foreground right now, and you do not want this receiver to cause jank.
So, the recipe became:
Have the alarm trigger a BroadcastReceiver
Have the receiver acquire a WakeLock
Have the receiver delegate the work to a Service, typically an IntentService
Have the service release the WakeLock when the work is completed
My WakefulIntentService was the first library to offer support for this recipe. WakefulBroadcastReceiver came along later. They both accomplish the same end, just with different semantics.
Note that "why don't we just acquire a WakeLock in the service?" fails because the device might fall asleep between the end of onReceive() and the first place where the service might get a chance to acquire the WakeLock.
Now, for other situations, not involving AlarmManager, having the service manage its own WakeLock is perfectly reasonable. In fact, that's one of the reasons I went with having a special IntentService (WakefulIntentService) rather than a special BroadcastReceiver (WakefulBroadcastReceiver).

Related

Aquire partial wakelock in a IntentService

My IntentService gets fired from 2 places, either by an Alarm or by the Activity and since the duration is related to the amount of data it needs do fetch from the web, from what I understood, I need to keep a partial wakelock.
Here's my implementation:
#Override
protected void onHandleIntent(Intent intent) {
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WakeLock");
try {
wakeLock.setReferenceCounted(false);
wakeLock.acquire(3600000);
///other code here
}
catch{
}
finally{
if (wakeLock.isHeld()) {
wakeLock.release();
}
}
My question is: will this work good enough? Will the finally make sure that the wakelock is released in any circumstances? From what I know onHandleIntent handles intent one after another, so there is no risk in having 2 intents/2 wakelocks in the same time.
Later edit:
The IntentService is called in 2 ways:
from my Activity, like
startService(new Intent(context, MyService.class).putExtra()..);
2 from a triggered Alarm using a PendingIntent
PendingIntent pendingIntent = PendingIntent.getService(context, someId, myServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Will the service have enough time to aquire wakelock when ran from the Alarm?
Whether you need to keep wake-lock or not should not be related to the amount of work your Service does - theoretically, device can go to sleep even if the amount of work is small.
Wake-locks should be considered only if you absolutely must ensure that device can't sleep while the Service is running. Cases like this are very rare. Some examples:
Alarm clock applications (need to wake you up even if the device is sleeping)
Real time messaging applications (need to notify you about new messages even if the device is sleeping)
Most applications don't have such a strict timing requirements. For example, the following are NOT good reasons to use wake locks:
Periodic synchronization of data with the server (should be delayed until device awakes)
Displaying current user's location on map (can be obtained when device awakens; but wake-lock will be needed for applications that monitor user's entire route)
If you really need to ensure that the device doesn't sleep during Service execution, then you need to acquire a wake-lock (one of the several types). Let's assume that this is the case here.
You want to be able to start the "wakeful" Service from application's UI (Activity), and using AlarmManager.
Starting from UI
Since the device should be completely awake in order for the user to interact with UI, you can safely assume that if you start the Service in response to UI interaction it will have a chance to acquire a wake-lock (but do it as soon as the Service is started).
Your solution covers this case.
Starting from AlarmManager
Unfortunately, there is no guarantee (at least no documented guarantee) that when AlarmManager starts the Service it will hold a wake lock and allow the Service to acquire its own wake-lock. This means that the device can go to sleep after alarm fired, but before your Service had a chance to acquire the wake-lock.
This means that your solution will "break" in this case.
The only documented scheme in which AlarmManager will help you with wake-locks involves broadcasts:
The Alarm Manager holds a CPU wake lock as long as the alarm
receiver's onReceive() method is executing. This guarantees that the
phone will not sleep until you have finished handling the broadcast.
Once onReceive() returns, the Alarm Manager releases this wake lock.
This means that the phone will in some cases sleep as soon as your
onReceive() method completes. If your alarm receiver called
Context.startService(), it is possible that the phone will sleep
before the requested service is launched. To prevent this, your
BroadcastReceiver and Service will need to implement a separate wake
lock policy to ensure that the phone continues running until the
service becomes available.
This is where WakefulBroadcastReceiver comes in very handy.
Note that if you use this scheme, then there is no need to support a different one for "UI initiated" case - use the same approach in both cases.
You might also want to take a look at this library developed by #CommonsWare (I didn't use it myself though).

Why to put the WakeLock mechanism in BroadcastReceiver instead of having it in the Service?

I am having a look at the sample code from Google team for Android which is WakefulBroadcastReceiver
My question is is there a specific reason to have this mechanism acquire/release in BroadcastReceiver instead of putting this inside the Service itself. If yes what is it
?
It's very useful for something like alarms (see AlarmManager) or other types of PendingIntent use cases. With alarms send to BroadcastReceivers, the alarm manager mechanism ensures that the system will wake long enough to deliver the broadcast Intent (e.g. run the onReceive() method) to BroadcastReceivers only.
If you were to use a PendingIntent for a Service in this case, the Service would get "started" from an API perspective, but would not necessarily run because the system could go right back to sleep. Using a WakefulBroadcastReceiver, you could instead have the alarm trigger it, take the wake lock and start your Service. The Service would then get an opportunity to run and would ultimately need to release the wake lock so the system could go back to sleep.

Does AlarmManager require the PendingIntent to be of Type BroadcastReceiver?

The documentation for AlarmManager seems to imply (but does not outright explicitly require) that the PendingIntent you pass in to any of the set() methods should be of the type BroadcastReceiver, but I tested passing in other component types (like an IntentService) and it seemed to work fine.
Is it safe to use non-BroadcastReceiver Intents with AlarmManager?
Yes, and it has always worked, but I suspect not in the way that you're thinking. You can use any PendingIntent with an alarm; this could indeed be an activity or service PendingIntent. If it's a service PendingIntent, then the OS will call startService() for you when the alarm fires. The hidden catch is about the behavior of wakeup alarms.
When any alarm fires, the OS holds a wakelock on the sender's behalf for as long as it takes to deliver the PendingIntent, at which point the wakelock is released and the device is allowed to go back to sleep. The exact meaning of "as long as it takes to deliver" depends on which kind of PendingIntent is being used.
Broadcast delivery is essentially treated as synchronous: the wakelock is held by the Alarm Manager until the recipient's onReceive() callback returns. This gives you a hard guarantee that whatever processing you want to do in onReceive() is guaranteed to proceed without the device sleeping.
However, activity and service PendingIntent delivery does not wait for the recipient in the same way. With those kinds of alarm PendingIntents, the device remains awake long enough to begin the process of starting the target activity or service, but then it can (and does) go back to sleep immediately after that launch has begun, before the target code actually has a chance to run. In practice this means that with a service PendingIntent, even if the alarm is a wakeup alarm, the service will often not actually execute until the device as a whole is woken up normally, e.g. the next time the user turns on the screen manually.
Sometimes this is okay, if your code doesn't actually care that even though the alarm fired at 3am, the service didn't start running until 7am when the alarm clock went off and lit up the phone for an extended period. More often, though, what apps need to do is use a broadcast alarm, then in their onReceive() -- knowing that the device will sleep as soon as they return -- acquire their own wakelock and start up the service under that wakelock, etc.
There is a terrific support library class called WakefulBroadcastReceiver that encapsulates this alarm-wakelock-service dance and makes it both easy and bulletproof; it's https://developer.android.com/reference/android/support/v4/content/WakefulBroadcastReceiver.html. Use that if you ever want to start a service in response to a wakeup alarm.

Do BroadcastReceivers need to be their own process to be independent from the UI (activities)?

So I'm working on an app that uses the AlarmManager to broadcast an intent for my receiver to do a simple task and finish quickly. I want my receiver to run every few minutes so I use setRepeating to ensure this. I don't want to have to worry about the main activity of my app running or being visible when the alarm triggers. In order for my receiver to run separately from the main activity like that, do I need to add android:process=":remote" to the receiver in the manifest? or are they already inherently separate things? The two do not need to communicate. I'm trying my best to kill the activity without canceling any alarms and the receiver seems to be running properly for now, but I'm wondering if it'll continue to work for a few hours or days.
Yes, they're separate. No need to use that attribute.
By the way, how much work do you do in that BroadcastReceiver? Normally, you can't do very much inside the BroadcastReceiver itself, you use it to trigger other things.
Also, I'm wondering how you're doing the following:
"I'm trying my best to kill the activity..."
I'm wondering what problem you were trying to solve here by trying to kill the activity?
To your immediate question - certainly not - it will be a performance killer and is unneeded anyway.
To your design - flawed. You should :
Register an alarm (also take care to re-register it on boot) - see wakeLock does not wait for network connectivity for the code registering the alarm
When the AlarmaManager wakes your receiver up delegate to a WakefulIntentService. The intent service is NOT guaranteed to run when the device is asleep (see Android deep sleep and wake locks).
See also:
PowerManager.PARTIAL_WAKE_LOCK android
PowerManager wakelock not waking device up from service for the WIS skeleton
WakefulIntentService implementation clarifications

using broadcast recevire with alarm manager in android

Why is it suggested generally to pass a pending intent for an Intent Service when using alarm manager? The same thing can be done in the onreceive() function of the broadcast receiver called by the alarmmanager. What is the advantage with using a service(Intent Service)?
If everything that you need done can be completed in onReceive of a BroadcastReceiver, then you should use that, not an IntentService.
If you want to do anything after the BroadcastReceiver, then you should use the IntentService. For example, if you want your BroadcastReceiver to start a Service, and you want the service to gain a WakeLock, then you should be using an IntentService instead.
The reason is that AlarmManager only guarantees that the onReceive of a BroadcastReceiver will be run, even if you use RTC_WAKEUP. So, it is slightly possible that if you use the BroadcastReceiver/Service combination, then the CPU will fall asleep before the Service can acquire the WakeLock - this is, unless you acquire a WakeLock in the BroadcastReceiver and you acquire one in the service, perhaps via a static WakeLock. But this is... messy, I suppose.
Btw, I have never implemented an IntentService. I just use the BroadcastReceiver and Service combo and have never had a problem reported. All the information I provided are things I read from other SO posts (primarily from CommonsWare)
EDIT:
The 50ms time frame I read from something CommonsWare posted on StackOverflow, and CommonsWare seems to be a rather reliable source of knowledge for Android.
I looked it up and, The docs do say:
(there is a timeout of 10 seconds that the system allows before
considering the receiver to be blocked and a candidate to be killed).
And they also say:
If this BroadcastReceiver was launched through a tag, then the object is no
longer alive after returning from this function.
You should not do anything that takes close to 10 seconds, just to be safe.
If you do anything that has to wait for a response, the BroadcastReceiver will die because the onReceive will likely finish running before you get the response back.
Though, I suppose the reason for the 50ms time frame is so you don't risk causing an ANR or any lag. Because if you use a Service, then you can start a new Thread, and it will not block. You would not be able to start a new Thread in a BroadcastReceiver because the code after the thread would continue to run, the BroadcastReceiver would die, and then the Thread would die, too.

Categories

Resources