One of the fundamental differences between Android and every other mobile operating system is the practically unrestricted capability to run services. Without this freedom we could not enjoy something as powerful as a homescreen widget or as straight-forward as a Twitter client with background updates. Aside from games and very simple utilities, it’s becoming increasingly rare to find an app that doesn’t run a service, at least for a short span of time. However, a bug has snuck into recent versions of Android and it can cripple background processing in some apps and widgets.
Note: There are some technical details ahead, some of which may only matter to developers. Even if you don’t write code, don’t turn away. Everything should still be very easy to read and comprehend.
A great advantage to running a service is that an app’s interface can be closed but the process can continue running. This allows apps and widgets to rely on services to be operational without the user having to start up an app manually. Even in circumstances where a service has to be shut down -like freeing up memory for a game- the system can restart it later. The same rules apply to closing an app from the Recents screen (aka. task switcher or multi-tasking screen). This is all supposed to work automatically, so long as stopWithTask is set to false, which happens when the service returns START_STICKY or START_REDELIVER_INTENT from onStartCommand.
The troubles occur when a service is shut down by the system, either as a result of low memory or a user stopping it through the Recents screen. The system is supposed to automatically restart the service, but this bug prevents that from happening. Furthermore, BroadcastReceivers tend to become unresponsive when this bug comes into effect. This behavior can be a real problem to apps and widgets that expect to query for data from the Internet or update the content displayed on their widgets. Unfortunately, there really aren't any other obvious signs that something has gone wrong.
I’ve written a sample app to demonstrate the issue (and a workaround). As long as the service is running, it will produce a toast message every few seconds. If you would like to try it for yourself, the apk can be downloaded here, and the full code is available on GitHub. Take a look at the app and bug in action.
What Is Affected And How
The bug was introduced in Android 4.4.1 during a series of small patches meant to fix some other issues related to the service lifecycle. The side-effects did not become evident until after 4.4.2 had begun rolling out shortly after. Stock Android is definitely affected, meaning most Nexus devices will present with the issue, but it looks like some OEMs have independently fixed the issue before shipping their KitKat updates.
Strictly speaking, this bug only affects permanent or semi-permanent services - those that are supposed to continue running after an app’s interface has been closed. No reason has been clearly established, but Google’s own apps seem to be immune to the issue.
The two most common reasons for a service to be killed are related to low memory or actions by the user. Since most modern smartphones and tablets are shipping with 2 GB of RAM, it takes quite a powerful game or a lot of background services before the system will step in to remove something. Still, if you ever come out of a game and notice your widgets have become stale, this is probably the reason.
The far more common catalyst for this bug is the Recents screen, which allows users to dismiss apps. This action typically kills and restarts any services attached to the app, acting sort of like a reset switch for apps when they aren’t behaving properly. Since this can only be done with apps that have a UI in the Recents screen, services without an app are generally safe.
There are really aren’t many things regular users can do to reduce the effect of this bug. To reduce the likelihood of it occuring, don’t swipe away apps from the Recents screen any more than necessary. I hate that prospect as much as anybody, but it helps. If a service is killed, simply starting up the app that owns it should be enough to get it started back up. In the event that low memory is responsible for killing a lot of services, it’s probably just easier to restart the device and allow everything to come back on its own.
Developers do have an option for working around this issue in their own apps, but it’s not exactly ideal for all cases. While BroadcastReceivers and other similar mechanisms seem to be crippled, the AlarmManger can be used to refresh dead services. An alarm can be used to resurrect dead services. Of course, this approach isn’t without its drawbacks. Not only can adding alarms or using them for different purposes add complexity to an app, it can lead to higher battery consumption if the the device is repeatedly woken up from sleep.
As I mentioned earlier, my sample app implements a workaround using AlarmManager. Every app has different requirements, so this should not be considered a complete solution, rather a demonstration that the workaround is viable. If you should decide to implement something like this in your own app, be careful to set the alarm for a much more reasonable delay.
Current Status: A Fix Is On The Way
Developer Liangcai Li of nVidia submitted a patch in mid-Feburary which has been officially merged into AOSP. It’s likely we’ll see this fix included with the next maintenance release or version update, but there is obviously no formal timeline for when that will roll out. There has yet to be a formal response in any of the AOSP Issue tracker threads. We still might have to wait a while for this fix to make its way out to Nexus devices (and possibly some OEMs), but it’s good to know that it won’t be left hanging for long.
Thumbnail Image Credit: Michael Kappel