Search
  • Prasad Pawar

Context.startForegroundService() did not then call Service.startForeground()

Updated: Feb 9, 2019

Android 8.0+ devices will occasionally throw this exception and crash your app! How to fix this? Lets see.

Launch of Oreo = death of services

Android Oreo brought in many features like picture-in-picture, notification channels, new dots on your launcher icons, etc. but it also brought in a lot of behavioural changes that called for modifications in your existing projects.


One of the changes was in the background restrictions imposed on the apps. Services are dead; before Oreo Chinese ROMs (like MiUi) would kill services (or your entire process for that matter) but now the AOSP behaviour renders services useless.

Now on Oreo, if you try to start a service when your app doesn't have foreground priority, it will give you a crash.


But Android provided a way namely startForegroundService() which devs can use to trigger a Foreground service even if the app is in background.


A sample notification for foreground service

This displays a notification in the system tray and essentially gives your app the foreground priority. But the important change here was, once you call startForegroundService(), you then have to invoke startForeground() in your services onCreate() or onStartCommand() (just like you did before to promote your regular services to foreground) BUT there is a time limit equivalent to the ANR (Application Not Responding) time.

If the system fails to trigger your service right away, you are getting this crash and your app process will be terminated!


I've seen this issue on some devices from Samsung, Xiaomi, Nokia, and a few others.


A lot of devs including me have filed this issue with Google to get no good follow-up from their team. Some Google employees say it is an expected behaviour and that devs should file these issues with the respective OEMs. Others keep following up to no good end.


But we have to find workarounds as it's our reputation at stake! So here is the one I'm using for now.


So, the first thing is, we won't be using startForegroundService() as it kills us in the end.

We will be using traditional startService() method to trigger the service.

Now, we need foreground priority and only then can we use this method.

So, we have two flows here:

1. If our app is in foreground, use startService() directly.

2. Else, bring app to foreground, then invoke startService().


Now the first part is, how do you know your app is in foreground?

- Create a base activity class that extends AppCompatActivity.

For this example, we will call it MyBaseActivity.class.

- Now, in your entire project, make all activities extend MyBaseActivity.

- In MyBaseActivity, maintain a static boolean (say isForeground) that is set when onResume() is invoked and reset when onPause() is invoked.

- Create a public static method to get this boolean (say isForeground()).


Now, the first part can be summarised as below:

if (MyBaseActivity.isForeground()) {

startService(new Intent(context, MyService.class));

}


For the second part, You can start an activity that is specifically designed to be created, trigger your service and then finish itself.

- Create a new activity (say ServiceTriggerActivity.class) and add a fancy loader or something that says 'Please wait...I am trying to fix an issue that google devs shouldn't have introduced in the first place!'.

- In the onResume(), call startService() to trigger your service.

- In your service, fire a broadcast saying 'service is up'. Listen to this broadcast in ServiceTriggerActivity.

- When you get the broadcast in activity, finish it.


Now the code changes to:

if (MyBaseActivity.isForeground()) {

startService(new Intent(context, MyService.class));

} else {

Intent intent = new Intent(context, ServiceTriggerActivity.class);

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

startActivity(intent);

}


Finally heres that meme I promised :p


Great! Now you have a fully functioning foreground service that will not crash and kill your process.

The only limitation is, you cannot trigger the service if device is locked or in doze/standby mode. So make sure the device is unlocked before you invoke this hack of ours!

How about the ACTION_USER_PRESENT broadcast?


Let me know your thoughts :)



4,607 views1 comment

© 2019 by Prasad Pawar.