In the Code Pollution series, I’ll be writing about topics where a coding anti-pattern may work tactically for an individual application, but strategically will be bad for Android as a whole, just as pollution may benefit one firm while harming many others.
In previous posts, I complained loudly about trying to write services that remain in memory all of the time, and extolled the virtues of using AlarmManager to convert those services into scheduled tasks (a la Linux cron jobs). That advice is still very valid, but there is one scenario that the previous posts do not take into account.
But, first, a word (or, perhaps, several words) about games.
Real-time games, like the ever-popular first-person shooter (FPS) genre, aim to push a high “frame rate”. The faster the frame rate, the smoother the animations will appear. Game developers go to great lengths to make sure their application does not stall unexpectedly due to its own code — for example, developers will avoid generating garbage and will try to run the garbage collector only at safe times.
That is all fine and well, but a persistent concern for game developers on Android is the impacts that external forces have on their frame rates. For example, a year ago, the big concern was garbage collection going on in other processes — garbage collection takes CPU time, even if that work is being done in a totally separate Linux process from the game itself.
To counteract this, the core Android team made some improvements in Android 1.6, relegating all background processing to a class that is capped in terms of CPU utilization, leveraging some Linux process and thread control frameworks. Garbage collection in those background processes will no longer hog the CPU. Hence, games can run with minimal interference…so long as background processing stays in the background.
What has come to light recently is that there are still circumstances when code that was in the background class will be promoted to the foreground, at least briefly, to prevent operating system stalls. The two documented scenarios are:
- BroadcastReceiver objects will be brought to the foreground for the onReceive() call
- Services will be brought to the foreground for onCreate(), onStart(), and onDestroy()
Hence, while normal background processing is CPU-capped, any processing done in the two areas shown above will be in the foreground class and could steal more significant CPU time from the game.
And this is where the AlarmManager pattern comes in.
AlarmManager triggers a BroadcastReceiver to get control, in onReceive(), at the time an alarm goes off…which could be in the middle of a game. If that receiver attempts to do significant work in onReceive() itself, it will impact the frame rates of games being played at that time. You have long been encouraged to keep onReceive() calls short — this is just another reason to keep these to the millisecond range, not several seconds.
The best pattern, overall, for using AlarmManager is to use the BroadcastReceiver as a bridge to an IntentService. The IntentService will do whatever significant work there is in a background thread, one that should remain part of the background class and therefore will not harm game play. The downside is that the IntentService may wind up being created, started, and destroyed, and those specific operations will run in foreground priority. So long as you do not add any logic of your own to those three callbacks — relying instead on IntentService’s stock implementation — the effect should be minimal…but there will still be an effect.
The ideal solution would be for your background code to realize that a time-sensitive foreground operation is underway, so you can perhaps disable your alarms outright, or at least dial back their frequency. This could be accomplished in the operating system, but it could also be done as a community standard. We could come up with a pair of broadcast actions (e.g., GAME_ON, GAME_OFF) that everyone aims to honor. A game would broadcast GAME_ON before the game begins; those implementing alarms would respond by disabling those alarms. Similarly, the game would broadcast GAME_OFF to notify background work to resume normal operation. We would need to work out the mechanics of handling the case where GAME_OFF was not broadcast, due to crashes or evil developers.
Regardless, for those of you who use AlarmManager, please do what you can to make sure that you do as little work as possible in the foreground windows of background processes. Our ability to frag rests in your hands.