Posted by Keith Smyth
This is the fourth in a series of blog posts in which outline strategies and guidance in Android with regard to power.
A process is not forever
Android is a mobile operating system designed to work with constrained memory and battery. For this reason, a typical Android application can have its process killed by the system to recover memory. The process being killed is chosen based on a ranking system of how important that process is to the user at the time. Here, in descending order, is the ranking of each class of process. The higher the rank, the less likely that process is to be killed.
Native | Native Linux daemon processes are responsible for running everything (including the process killer itself). |
System | The system_server process, which is responsible for maintaining this list. |
Persistent apps | Persistent apps like Phone, Wi-Fi, and Bluetooth are crucial to keeping your device connected and able to provide its most basic features. |
Foreground app | A foregrounded / top (user visible) app is the app a user is currently using. |
Perceptible apps | These are apps that the user can perceive are running. For example an app with a foreground service playing audio, or an app set as the preferred voice interaction service will be bound to the system_server, effectively promoting it to Perceptible level. |
Service | Background services like download manager and sync manager. |
Home | The Launcher app containing desktop wallpaper |
Previous app | The previous foreground app the user was using. The previous app lives above the cached apps as it's the most likely app the user will switch to next. |
Cached apps | These are the remaining apps that have been opened by the user, and then backgrounded. They will be killed first to recover memory, and have the most restrictions applied to them on modern releases. You can read about them on the Behavior Changes pages for Nougat, Oreo and Pie. |
The foreground service
There is nothing wrong with becoming a cached app: Sharing the user's device is part of the lifecycle that every app developer must accept to keep a happy ecosystem. On a device with a dead battery, 100% of the apps go unused. And an app blamed for killing the battery could even be uninstalled.
However, there are valid scenarios to promote your app to the foreground: The prerequisites for using a foreground service are that your app is executing a task that is immediate, important (must complete), is perceptible to the user (most often because it was started by the user), and must have a well defined start and finish. If a task in your app meets these criteria, then it can be promoted to the foreground until the task is complete.
There are some guidelines around creating and managing foreground services. For all API levels, a persistent notification with at least PRIORITY_LOW must be shown while the service is created. When targeting API 26+ you will also need to set the notification channel to at least IMPORTANCE_LOW. The notification must have a way for the user to cancel the work, this cancellation can be tied to the action itself: for example, stopping a music track can also stop the music-playback service. Last, the title and description of the foreground service notification must show an accurate description of what the foreground service is doing.
To read more about foreground services, including several important updates in recent releases, see Running a service in the foreground
Foreground service use cases
Some good example usages of foreground services are playing music, completing a purchase transaction, high-accuracy location tracking for exercise, and logging sensor data for sleep. The user will initiate all of these activities, they must happen immediately, have an explicit beginning and end, and all can be cancelled by the user at any time.
Another good use case for a foreground service is to ensure that critical, immediate tasks (e.g. saving a photo, sending a message, processing a purchase) are completed if the user switches away from the application and starts a new one. If the device is under high memory pressure it could kill the previous app while it is still processing causing data loss or unexpected behavior. An elegantly written app will detect being backgrounded and respond by promoting its short, critical task to the foreground to complete.
If you feel you need your foreground service to stay alive permanently, then this is an indicator that a foreground service is not the right answer. Many alternatives exist to both meet the requirements of your use case, and be the most efficient with power.
Alternatives
Passive location tracking is a bad use case for foreground services. If the user has consented to being tracked, use the FusedLocationProvider API to receive bundled location updates at longer intervals, or use the geofencing API to be efficiently notified when a user enters or leaves a specified area. Read more about how to optimize location for battery.
If you wish to pair with a Bluetooth companion device, use CompanionDeviceManager. For reconnecting to the device, BluetoothLeScanner has a startScan method that takes a PendingIntent that will fire when a narrow filter is met.
If your app has work that must be done, but does not have to happen immediately: WorkManager or JobScheduler will schedule the work for the best time for the entire system. If the work must be started immediately, but then can stop if the user stops using the app, we recommend ThreadPools or Kotlin Coroutines.
DownloadManager facilitates handling long running downloads in the background. It will even handle retries over poor connections and system reboots for you.
If you believe you have a use case that isn't handled let us know!
Conclusion
Used correctly, the foreground service is a great way to tell Android that your app is doing something important to the user. Making the right decision on which tool to use remains the best way to provide a premium experience on Android for all users. Use the community and Google to help with these important decisions, and always respect the user first.