October 24, 2014

Diamonds Are Forever. Services Are Not.

diamond2Android offers a “service” component. Unlike “activities” (screens) that interact directly with the user, services are more for managing background operations. The quintessential example is a music player, continuing to play tunes while its UI is not running.

Some developers then make the leap from “it is possible” to “it is a really good idea and should be used wherever”. Many people on Android Google Groups or StackOverflow propose to create services that “run forever” and ask how to prevent them from being killed off, and such.

These developers appear to ignore the costs of this approach:

  • While running, a service consumes one process’ worth of memory, which is a substantial chunk, despite Dalvik’s heavy use of copy-on-write techniques to share memory between processes. A single service hogging memory is not that big of a deal; a dozen such services will prevent the Android device from being usable. Hence, even if the memory footprint does not impact the developer directly, it impacts the users indirectly — much like how pollution benefits the polluter with a corresponding cost to society.
  • While running, the service will need to fight Android, which will want to shut down the service if RAM gets too tight. While Android should, in principle, restart the service once memory is plentiful again, the developer will have no control over the timing of this.
  • While running, the service will need to fight its own users, who may elect to shut down the service via the Manage Applications screen, or through a third-party task manager. In this case, Android will not restart the service automatically.
  • The service will fall asleep when the device falls asleep and shuts down the CPU, which means even if the service “runs forever”, it will not be able to run forever, unless it prevents the CPU from stopping, which wrecks battery life.

The recommended alternative is to use AlarmManager, as described in a previous post and in finer Android programming books. Think of your Android service as a cron job or Windows scheduled task, not as a persistent daemon or Windows service. This approach works much better with Android’s framework and device limitations:

  • The service only uses memory when it is actually doing something, not just sitting around waiting.
  • The odds that Android will need to kill off the service decreases, in part because the service will not be “old” and other services are likely to be killed first.
  • The odds that the user will need to kill off the service decreases, because the service is less likely to cause any pollution that may cause the user problems.
  • The service can hold a WakeLock for the duration of its bit of work to keep the CPU running for a short period

Now, the AlarmManager API is not the friendliest. Some developers get tripped up while trying to handle multiple outstanding alarms. While there are ways to deal with this (via careful construction of PendingIntent objects), sometimes you do not truly need more than one alarm. If you have code that you want to run every 15 minutes, and other code that you want to run every 24 hours, use one 15-minute alarm and trigger the once-every-24-hours code every 96th time the 15-minute alarm goes off.

I encourage Android developers to try to avoid long-running services wherever possible. If you are uncertain how to design an app to avoid the long-running service, post a clear description of the business scenario (not just low-level technical stuff) to the [android-developers] group or to StackOverflow with the #android tag. We can try to help you find ways of achieving your business objectives in an Android-friendly fashion.