Android 9.0: Not allowed to start service: app is in background.. after onResume()
AndroidAndroid ServiceAndroid 9.0-PieAndroid Problem Overview
I've got a music player which attempts to start a Service
in onResume()
of an Activity
. I've removed a few lines for clarity, but the code is effectively:
@Override
protected void onResume() {
super.onResume();
startService(new Intent(this, MusicService.class));
}
According to the crash logs, this is throwing an Exception on some devices running Android P:
Caused by java.lang.IllegalStateException: Not allowed to start service Intent { cmp=another.music.player/com.simplecity.amp_library.playback.MusicService }: app is in background uid UidRecord{6a4a9c6 u0a143 TPSL bg:+3m25s199ms idle change:cached procs:1 seq(1283,1283,1283)}
at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577)
at android.app.ContextImpl.startService(ContextImpl.java:1532)
at android.content.ContextWrapper.startService(ContextWrapper.java:664)
at android.content.ContextWrapper.startService(ContextWrapper.java:664)
at com.simplecity.amp_library.utils.MusicServiceConnectionUtils.bindToService(SourceFile:36)
at com.simplecity.amp_library.ui.activities.BaseActivity.bindService(SourceFile:129)
at com.simplecity.amp_library.ui.activities.BaseActivity.onResume(SourceFile:96)
How is it possible that my app is in the background, immediately after onResume()
(and super.onResume()
) is called?
This doesn't make any sense to me. Could this be a platform bug? All 3500+ users affected by this crash are on Android P.
Android Solutions
Solution 1 - Android
There is a workaround from Google:
> The issue has been addressed in future Android release. > > There is a workaround to avoid application crash. Applications can get > the process state in Activity.onResume() by calling > ActivityManager.getRunningAppProcesses() and avoid starting Service if > the importance level is lower than > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND. If the > device hasn’t fully awake, activities would be paused immediately and > eventually be resumed again after its fully awake.
So I think it should like that:
// hack for https://issuetracker.google.com/issues/113122354
List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
if (runningAppProcesses != null) {
int importance = runningAppProcesses.get(0).importance;
// higher importance has lower number (?)
if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
URLPlayerService.startActionBroadcastServiceData(PlayerActivity.this);
}
I have used handler as a workaround and it works pretty good but not 100%:
// hack for https://issuetracker.google.com/issues/113122354
handler.postDelayed(() -> URLPlayerService.startService(PlayerActivity.this),200);
Solution 2 - Android
UPDATE: This is working for us in Prod, but it's not 100%. I have received one crash report over the past month and a half when there would have been well over a hundred otherwise. Until this is properly fixed, this seems like our best option for now. Maybe if I raised the time beyond 300 that one crash would never have happened?
We're testing this out right now which so far seems to be working. Will update as we see more results
class ResumingServiceManager(val lifecycle: Lifecycle) : LifecycleObserver {
init {
lifecycle.addObserver(this)
}
val disposable: CompositeDisposable = CompositeDisposable()
fun startService(context: Context, intent: Intent) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent)
} else {
Single.just(true)
.delaySubscription(300, TimeUnit.MILLISECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = {
context.startService(intent)
}
).addTo(disposable)
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopped() {
disposable.clear()
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() {
lifecycle.removeObserver(this)
}
}
In onCreate()
initialize it and then anytime you want to start a service in onResume just call resumingServiceManager.startService(this, intent)
It's lifecycle aware so it will clear the disposable if it pauses cancelling the onSuccess from triggering when it might be on the way to the background with an immediate open/close.
Solution 3 - Android
This has been marked as 'fixed' in the Android Issue Tracker:
Presumably the fix will be released in one of the Android Q releases.
According to the Googler who closed the issue,
>There is a workaround to avoid application crash. Applications can get the process state in Activity.onResume()
by calling ActivityManager.getRunningAppProcesses()
and avoid starting Service if the importance level is lower than ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
. If the device hasn’t fully awake, activities would be paused immediately and eventually be resumed again after its fully awake.
Solution 4 - Android
our team faced with the same problem. My calendar shows 5 of April, 2019, but problem still reproduce on my Samsung Galaxy S9+, android 9.0 (One UI)
We start service in onResume and unbind onPause.
How to reproduce
Just lock your device, when activity with this logic is on the screen, and don't touch 10-15 minutes. After unlock screen, app will crash.
How to fix
We found solution that actually work. Since android 8+, start your service in android.os.Handler.post(...)
Example (Kotlin):
override fun onResume() {
super.onResume()
Handler().post {
val serviceIntent = Intent(activity, SomeService::class.java)
activity?.startService(serviceIntent)
activity?.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
}
}
Good luck!
Solution 5 - Android
I found a way to reproduce the problem.
Tested on Android Emulator with Android 10, 11, 12 DP2:
- Launch the app which starts a service in
Activity.onResume()
- Press the Home button to change activity's state to stopped
- Wait until the system destroys the service (logcat log will appear: "ActivityManager: Stopping service due to app idle")
- Open Android settings -> System -> Gestures -> System navigation
- Change the system navigation option to a different one
- The application will crash
I also created a new issue in the Android Issue Tracker with these steps: https://issuetracker.google.com/issues/183716536
Solution 6 - Android
It seems that the error message is ambiguous, and what I've found is that the last part of the message is referring to the application where the service resides, and NOT the application that is attempting to start the service and bind to it.
The error I get if the service's application is not foregrounded (even though the service itself is a background service):
W/System.err: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.mycompany.myserviceapp/.MyService }: app is in background uid null
at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1505)
at android.app.ContextImpl.startService(ContextImpl.java:1461)
at android.content.ContextWrapper.startService(ContextWrapper.java:644)
Now, if I run the application that has the service so it's in the foreground immediately prior to launching the "client" app that will attempt to start the service, everything works fine for the service start.
If the service is a "background service" and the app for the service is not in the foreground, the service may be terminated - so getting the service started isn't really enough if you need it to stay around.
This seems to be a result of the changes from Android 7 to Android 8 where in Oreo they began restricting things a bit..
It's all explained here in the Android docs on the Background Execution Limits of Oreo
The recommendation in the docs is to migrate your logic to scheduled jobs, OR make the service a foreground service - where there will be a visual indication that the service is running.
Solution 7 - Android
Maybe android.arch.lifecycle could be used as a workaround for this Android 9 bug?
public class MyActivity extends Activity implements LifecycleObserver {
protected void onResume() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
startService(intent);
} else {
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
} else {
startService(intent);
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onEnterForeground() {
startService(intent);
ProcessLifecycleOwner.get().getLifecycle().removeObserver(this);
}
}
[I've found it here.][1]
[1]: https://github.com/mapbox/mapbox-events-android/pull/157/commits/afb487fe2433368abc7ec9c0d4be7248c03adf6f "Found here"