

Your guide to foreground services on Android
source link: https://www.hellsoft.se/your-guide-to-foreground-services-on-andorid/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Your guide to foreground services on Android
Android foreground services are difficult to get right. In this post, I'll explain the restrictions and what you need to do to get them working properly.
Erik Hellman
Photo by Erik Mclean / Unsplash
The Service
component have been around on Android since the very beginning. It is the component we need in order to be able to perform operations that are not immediately triggered through an Activity
. However, over the years Google has restricted how services can be launched and how long they can run. This is mostly to prevent excessive battery consumption, but also for user privacy. Google also recommends developers consider using the Work Manager instead, which is usually a good recommendation.
However, there are legitimate reasons for using a Service
in your app today. Your app might be playing audio (like music, podcasts, or audiobooks), or it might be a companion app for a Bluetooth peripheral (like a smartwatch, eBike, or smart lock on your front door). While you still should consider using the Work Manager for any background work, you sometimes must resort to a service that keeps your app running indefinitely.
In later Android versions, Google introduced the concept of foreground services. These are services started with the intention of performing actions on the users' behalf without presenting a full UI. In order to use them, they must be bound to an active notification so the user is aware that there is something running in the background.
Let's look at how to properly implement a foreground service, and how to do it correctly for all Android versions.
Starting a foreground service
The first thing to know is how to start a foreground service. Before this concept, we only had Context.startService()
for starting services, but that has now changed. When you need a Service
that should keep running when the UI is no longer active, you need to instead use the method Context.startForegroundService()
.
In fact, as from API level 26 (Android 8), you should never use the legacy Context.startService()
method at all. If you look at the documentation for this method you'll find the following note:

The Android API docs explain why you should avoid frequent calls to startService()
In essence, you should avoid controlling your service by passing an Intent
and starting it, as this is an expensive operation. If you need to do frequent interactions with a Service
, use a bound service instead.
Starting a foreground service begins by simply calling Content.startForegroundService()
with an explicit Intent
.
fun launchService(context: Context) {
val intent = Intent(this, MyForegroundService::class)
context.startForegroundService(intent)
}
Next in our service, we must implement onStartCommand()
properly. This is where a common source for making a mistake.
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
createNotificationChannel(applicationContext)
val notification = createNotification(applicationContext)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
)
} else {
startForeground(NOTIFICATION_ID, notification)
}
return START_STICKY
}
The basics for how your onStartCommand()
in a foreground service should look like.
The important part is when you call startForeground()
in onStartCommand()
. This needs to be done as soon as possible and never deferred until later. If you're concerned about the content of the notification you should use default values at this point and update the notification later when the values are available.
Never call startForeground() from a background thread, like a coroutine. Always do it as soon as possible from onStartCommand().
It is also important to use the version check and call the correct Service.startForeground()
method. A new method was introduced in API level 29, and that one must be used if your app targets API level 29 or later. This method adds a third parameter, foregroundServiceType
, which is either a subset of what you've specified in the manifest (see below) or the value ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
which tells the system to use all the service types you specified in the manifest. Unless you start your service in different ways depending on the situation, I recommend always using ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
in order to make your implementation simpler.
In your manifest, you have to include the foregroundServiceType
attribute for the entry on your service, or it will not function as a foreground service from API level 29 and later.
<service
android:name=".MyForegroundService"
android:exported="false"
android:foregroundServiceType="connectedDevice"
android:label="@string/my_foreground_service_name">
</service>
You now have a service implementation that fulfills the requirements for running as a foreground service. Let's look at when we can start this service and when we cannot.
Foreground service restrictions
Starting with API level 31 (Android 12), starting services became even more restricted. From that version, you're no longer able to call Context.startForegroundService()
from the background (like when receiving a broadcast when your app doesn't have an Activity
in the foreground), except for a few special cases.
Note: if you are doing anything regarding foreground services on Android, you must have a full understanding of these exceptions. Read the page linked above!!
I won't cover all these "special cases" in this post but instead, focus on the most common cases.
Activity pausing
The most common case is described as "Your app transitions from a user-visible state, such as an activity.". This usually means the user is switching to a different app or going back to the launcher. Let's assume that your app is an audiobook player. In this case, you would start your foreground service when onPause()
is called on your Activity
.
You could start your foreground service before this point, but this all depends on how you wish the notifications for your app to work. I recommend that you start your foreground service as soon as possible, so for apps playing audio, it could be done when you start the app or at least when starting the playback.
In fact, starting your foreground service when your activity is in the foreground is probably the best choice for most apps that need a service like this. If you have an app for tracking your running activities, you start the service when the user starts the tracking in the app.
High-priority FCM message
The second common case is when your app receives a high-priority messagefrom Firebase Cloud Messaging. A typical use case for this is messaging apps where the user receives a message that they should be notified about, even when the device is sleeping. If you believe that you need to run a foreground service at this point to keep your app running, this is a situation where that is possible.
However, if the user doesn't interact with the notification from the FCM message, the system will deprioritize future messages to a normal priority and you're no longer allowed to start a foreground service from these. You can tell if this is the case by checking RemoteMessage.getPriority()
on the received message and comparing it with RemoteMessage.getOriginalPriority()
. If you're no longer a high-priority message, you know your user isn't checking these notifications and you can't start a foreground service anymore.
For messaging apps, I would not recommend a foreground service in any case, since you already have FCM to deliver messages when you're app isn't running (which will launch your app). Keeping a network connection to your messaging service alive indefinitely is a waste of battery, so please be a good citizen and design your messaging app to rely on FCM instead.
Remember that your app won't be killed immediately when the user puts your app in the background, so you can still retain a network connection for a while to allow fast message delivery when the user switches between apps.
Companion apps
The third common case is when your app is a companion app to an external peripheral, like a smartwatch, bike, or smart home device. This is also the most common cause of errors regarding foreground services. You want your app to keep running while it is connected to the device. Also, when not connected you want to be woken up when the device is detected nearby by the phone.
The short answer is that you must use the CompanionDeviceManager
for all cases where you want to start a foreground service from a background state, and you're acting as a companion app to a peripheral. I've seen all sorts of efforts to get around this restriction, but the harsh truth is that you cannot do this reliably from Android 12 without the CompanionDeviceManager
.
Let's assume it's a Bluetooth LE peripheral that your app is connecting to. When onboarding your users, you should use the CompanionDeviceManager
to pair with the device. If you already have an app but aren't using the CompanionDeviceManager
you must still perform this pairing. To make that situation easier in these situations, you simply use a ScanFilter
with the device address that you already know and call setSingleDevice()
when creating the AssociationRequest
. The latter will tell the CompanionDeviceManager
to search among already bonded devices and is specifically there to handle these cases.
When using the CompanionDeviceManager
, you should also add the related permissions to your manifest. At least, you probably want to include the following.
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" />
The two common permissions to add when using the CompanionDeviceManager
Their purpose is fairly self-explanatory so I won't go into the details, but it is important to understand that these permissions won't be applied unless you have an association with a peripheral created with the CompanionDeviceManager
. You can confirm this by fetching the current associations. If that list is empty, you need to perform the association step.
Once you have the device pairing created, you have two methods for being woken when the peripheral is detected by the phone. You either use the BluetoothLeScanner
and register a PendingIntent
that gets triggered when the device is detected, or you use a CompanionDeviceService
together with CompanionDeviceManager.startObservingDevicePresence()
. The latter is only available from API level 31 (Android 12). While you still can use BluetoothLeScanner
on Android 12 and above to trigger the foreground service, I do recommend using the CompanionDeviceService
when it is available.
This will effectively mean that your app will have two different foreground services, one that extends the regular Service
class and one that extends CompanionDeviceService
. I recommend keeping two bool
s in your resources that let you toggle the enabled
attribute in the manifest depending on the API level. The following code snippets show how this could look.
<resources>
<bool name="enable_legacy_service">false</bool>
<bool name="enable_cdm_service">true</bool>
</resources>
Bools in values-v31 resource folder
<resources>
<bool name="enable_legacy_service">true</bool>
<bool name="enable_cdm_service">false</bool>
</resources>
Bools in default values resource folder
<service
android:name=".peripherals.SampleCompanionService"
android:exported="true"
android:enabled="@bool/enable_cdm_service"
android:foregroundServiceType="connectedDevice|"
android:label="@string/peripheral_device_service_name"
android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
<intent-filter>
<action android:name="android.companion.CompanionDeviceService" />
</intent-filter>
</service>
<service
android:name=".peripherals.BackgroundScanPeripheralService"
android:exported="true"
android:foregroundServiceType="connectedDevice"
android:enabled="@bool/enable_legacy_service"
android:label="@string/peripheral_device_service_name">
<intent-filter>
<action android:name="se.hellsoft.foregroundservices.peripherals.START_SERVICE" />
</intent-filter>
</service>
The service definitions in the manifest where the enabled attribute is toggled based on the API level.
Let's sum up the case for companion devices and foreground services: You must use the CompanionDeviceManager
to be able to start a foreground service when the device is detected and your app isn't running in the foreground. Even when you have an app with existing pairing to a device, you still need to create a companion pairing in order to be granted the necessary permission.
Use the BluetoothLeScanner
with a PendingIntent
before API level 31 to wake up your app and start the foreground service when the device is detected. From API level 31, you instead should use the CompanionDeviceService
to detect the device presence.
The device rebooted or the app updated
There are some other exceptions that might apply to your app as well and which would let you start a foreground service from the background. These are usually complementary to one of the cases described above.
If your app gets updated, a special broadcast ACTION_MY_PACKAGE_REPLACED
will be sent to your app (if you have a receiver registered in the manifest for this action) and you will be allowed to start your foreground service from there. Also, if your app listens for ACTION_BOOT_COMPLETED
or ACTION_LOCKED_BOOT_COMPLETED
you can also start a foreground service from the receiver.
These three broadcasts are useful for companion apps where you need to check if you can connect to the device immediately, or otherwise resume background scanning after a reboot or when your app was updated. It can also be useful for messaging apps to reconnect to the service after reboot to see if there are any new messages since any FCM message might have been lost during the reboot.
Asking the user to turn off battery optimizations (Please don't!)
You can also ask the user to disable battery optimizations for your app by directing them to the system's settings through the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
intent action. I strongly advise against this practice as you rarely need to do this if you follow the guide above. It will probably also result in bad battery performance for the user and they will be able to see that your app is a likely cause for this, resulting in bad reviews and lost users.
Since you usually don't need to do this, I recommend that you try to apply one of the patterns above to your app. Most likely, a creative interpretation of the use of foreground services will make for a better user experience overall.
Use the Work Manager instead
While there are legitimate reasons for starting foreground services from the background, most developers haven't properly explored the option of using the Work Manager instead.
Any periodic work that doesn't need exact timing should be done with the Work Manager instead of a foreground service. If your app needs to transfer a large amount of data, then use the option for long-running workers to do so instead of a foreground service.
Conclusions
Getting foreground services to work correctly on Android is a difficult task, and it keeps getting more complicated with each new Android version as Google keeps adding and modifying the restrictions.
We can already now see what the new restrictions for foreground services are coming with Android 14, so make sure you're already updated with what is necessary for earlier versions and your life as an Android developer will be easier once Android 14 is released.
The most common mistake I see developers do is to try to bypass the restrictions or to simply yield and ask the user to disable battery optimizations. I hope this post will help more developers to overcome the challenges and get their foreground services working correctly.
Recommend
-
37
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 opera...
-
3
Closed Bug 1618547 Opened 1 year ago Closed 29 days ago
-
12
How to configure Foreground Color in a single ListBox item? advertisements I'm doing a game. I have a list of users (nicks): Li...
-
4
How to get a toast notification when the app is running in the foreground in wp8 advertisements I want to implement the "toast" notification i...
-
10
Problems with the life cycle of the activity (How to detect if the activity is in the foreground) advertisements I have broadcast receiver whi...
-
5
Introduction In a previous tutorial, we discussed how the ps, kill, and nice
-
7
Modus themes for GNU Emacs (Modus Operandi and Modus Vivendi) A pair of highly accessible themes that conform with the WCAG AAA standard for colour contrast between background and foreground combinations (a minimum contrast of 7:1---...
-
8
How to stop foreground services from the notification drawer in Android 13
-
4
android:foreground 最近一段时间研究了一下plaid,想学习一下material design。 这里记录一下view的background和foreground。 平时的话我们设置点击效果,...
-
6
Migrate foreground services to user-initiated data transfer jobs bookmark_border...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK