Pitfalls of a foreground Service lifecycle

Maciej Witowski
Published in
3 min readDec 29, 2018

--

As part of Google Play’s target API level requirement existing apps need to target at least Android 8.0. One of the migration steps is related to background services: starting a Service when the app is in the background requires calling startForegroundService() instead of startService().

Docs say that using the former is:

an implicit promise that the Service will call startForeground(int, android.app.Notification) once it begins running.

There are a few situations though when fulfilling this promise may surprise us.

1. Not calling startForeground() early enough

So what happens when startForeground() isn’t called at all?

Docs again:

The service is given an amount of time comparable to the ANR interval to do this, otherwise the system will automatically stop the service and declare the app ANR.

This is only partially true. Only when the app is in the background it behaves as the docs say after the ANR interval and we see:

ActivityManager: ANR
Reason: Context.startForegroundService() did not then call Service.startForeground()

If the ANR interval passed and the app is in the foreground, it will (after ~10 seconds in my experiments) miserably crash with:

FATAL EXCEPTION: main
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()

For both of these cases the solution is to call startForeground() at the very beginning of onCreate(), to ensure some long-running operation won’t delay it.

2. Calling stopSelf() before startForeground()

Sometimes we need to stop the Service from within. This can happen when some kind of finishing Intent is received in onStartCommand() or system’s state changes (eg. user logs out and what service does is not relevant anymore). In such cases we call stopSelf(). Calling this method before startForeground() however leads to the same crash as in the previous point.

The crash still occurs even if startForeground() is actually invoked, like in this purely theoretical scenario:

stopSelf()
startForeground(ID, notification)

Use the solution from point 1 to ensure startForeground() was called before stopSelf().

3. Stopping a Service from another place in the app

In some scenarios, we may want to stop the Service from another place in the app eg. UI button or some other background operation. The principle of always calling startForeground() before stopping a Service applies here as well.

However there’s a one tricky case: if some code calls stopService() before Service’s onCreate() callback is invoked, there’s no way to call startForeground() yet and the app will crash.

As with many lifecycle issues, this one can be tackled with a finite number of boolean flags.

We can use a helper class like this:

We can then keep a reference to ForegroundServiceLauncher in our Service’s companion object. Our Service’s code looks as follows:

The downsides are that we need to remember to always call SampleService.start() and SampleService.stop() methods instead of Context’s startForegroundService() and stopService(). Another is the boilerplate in our Service. Fortunately, there were only few cases when we needed to stop the Service from another place in our app, therefore we decided not to invest time in a better solution.

As we can see even a seemingly simple contract can lead to subtle issues, which if not developed and tested thoroughly can appear in production.

If you found similar problems or better solutions to the ones I mentioned, let me know in comments. Thanks!

--

--