The Role of Services
“Only have a service running when it is actively delivering value to the user”.
I use the above expression a lot, as it is the shortest way I have found to express when using a service is appropriate. However, as with all short explanations, it is not necessarily intuitively obvious what I mean.
Primarily, a service
exists as a flag to the operating system, indicating that you are
actively delivering value to the user, and therefore Android should
aim to terminate other processes — not yours — when it
gets low on system RAM. Secondarily, a service provides a couple
of APIs for IPC:
the “command” pattern (Intent
and startService()
)
and the “binding” pattern (bindService()
).
While we also use those two communication patterns for in-process use, that is mostly in support of the role of flagging our work’s existence to the OS. We organize our code around those communication patterns so that the service is responsible for the “actively delivering value” task, and so when the service is no longer doing that, we can stop the service.
When we have a service running, therefore, we are indicating to
Android that we think that the user thinks that our process is
more important to the user than are other competing processes, and
so the user would prefer that Android terminate those competing
processes rather than ours. This affects multitasking, as the
more service-laden processes are around, the more quickly that
ordinary processes the user might have used recently will be terminated
just to free up RAM. This becomes particularly obvious to the user
when the user tries switching back to the app and something is lost
due to the process termination (e.g., because the app is not handling
onSaveInstanceState()
well).
The catch is that too many developers don’t think in terms of the user. They think in terms of themselves, believing that what is important to the developer must be important to the user. Sometimes, what is important to the developer works at cross-purposes to what is important to the user. For example, the developer wants to go home rather than clean up some sloppy service code, while the user wants multitasking to work well. What the developer wants (less work in cleaning up the service usage) and what the user wants (fewer processes with running services) are at odds.
Hence: only have a service running when it is actively delivering value to the user.
Here, “value”, like beauty, lies in the eyes of the beholder. And the user thinks the user is the beholder, and so the user is the one defining what is, and is not, “value”. The user must feel that what your service is doing for them is worth the worse multitasking, where they have to start up “Angry Birds: The Battle of the Five Flocks” again from the beginning, rather than return to their in-progress game, because Android terminated the birds’ process when the user took a phone call, rather than terminate your service’s process.
“Actively delivering” means that the service is delivering value right now. Not “maybe in a few minutes”. Not “well, sometime, but we don’t know when”. Right now. And the recipient of that “value” must be the user — you, the developer, are not more important than the user.
So, let’s work through some scenarios:
-
Having a service around for playing back the media for your music player app is actively delivering value to the user. The user will notice if the music were to all of a sudden stop due to your process being terminated, and so indicating to the OS that you are delivering value to the user is reasonable. In fact, this is one of a handful of scenarios where using
startForeground()
is relevant. -
Having a service around to try to keep your process around rather than have to reload data is not actively delivering value to the user. Your process will not live forever anyway, and so you should be fixing the data-load speed issue to handle those process startups. That, in turn, will alleviate your need to try to artificially keep the process around to avoid the data-load work. The value to the user is having the data be loaded, but the long-running service should not be required to deliver that value.
-
Having a service around to download a large file is actively delivering value to the user, assuming that they value the file. This is an ideal use case for an
IntentService
, as you need a background thread anyway for the network and disk I/O, and the service can automatically shut itself down when the download is complete. That allows you to have the running service only while you are actively delivering value to the user, while having the service go away once the value goes away. -
Having a service around all the time to do work periodically is not delivering value to the user, in most cases. While the periodic work is value, watching the clock tick in between pulses of work is not delivering value. Instead, use
AlarmManager
, or the new “L”JobScheduler
. -
Having a service around because you wanted to have a central point of control in your app is not delivering value to the user. Having a central point of control is reasonable, but you do not need it to be a service. For this role, an ordinary singleton is lighter weight, in that a service is a singleton that happens to have broader impacts with regards to your process lifetime.
-
Having a service around because… well, you thought that you needed a service, and so you put your code in one and started it, and then you couldn’t figure out when to stop the service, so you left it running… is NOT delivering value to the user. Do not create services because the mood strikes you. Create services because you are actively delivering value to the user when you are not in the foreground, not just “because”.
Usually, we have clear scenarios when we need to use the other three Android components:
-
Use an activity when you want to show something on the screen
-
Use a content provider when you want to share stuff with other apps that looks like a database or a stream
-
Use a broadcast receiver when you are looking to respond to a broadcast, usually issued by a third party
Services are a “Swiss army knife” that can be applied in lots of scenarios. As such, it is sometimes difficult to tell what is and is not a valid use of a service. If services did not have the “keep my process around” role, tossing services around willy-nilly would not be nearly as much of an issue. But services do impact the user and can reduce user satisfaction, and so it is important that developers think through why they are implementing a service, when that service should be running, and when that service should not be running.
And the simplest mantra that I have come up with to help developers work all that out is: “only have a service running when it is actively delivering value to the user”.