80

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.

Tim Malseed
  • 6,003
  • 6
  • 48
  • 66
  • 7
    I don't have a good answer for you but I can confirm we're seeing this as well. We've never reproduced it in-house, but similarly we're seeing it when starting the service in onResume(). I suspect this is a bug in Android P. – Kevin Coppock Aug 25 '18 at 02:51
  • 9
    OK, glad it's not just me. This issue has been reported: https://issuetracker.google.com/issues/113122354 – Tim Malseed Aug 25 '18 at 02:59
  • try in manifiest add process name in service tag – Ashvin solanki Aug 25 '18 at 05:03
  • I have 3 very popular apps with the same problem. It occurs only on Android P (Pixels and Essential Phone). I hope thats a bug that will be fixed. – Mateusz Kaflowski Aug 29 '18 at 10:46
  • 2
    Possible duplicate of [Android 8.0: java.lang.IllegalStateException: Not allowed to start service Intent](https://stackoverflow.com/questions/46445265/android-8-0-java-lang-illegalstateexception-not-allowed-to-start-service-inten). It was asked later, but has answer. – Access Denied Oct 08 '18 at 09:10
  • 3
    Also, Apps that target Android 9 or higher and use foreground services must request the FOREGROUND_SERVICE permission. This is a normal permission, so the system automatically grants it to the requesting app. From https://developer.android.com/about/versions/pie/android-9.0-changes-28 – Access Denied Oct 08 '18 at 09:13
  • @AccessDenied this is not a duplicate. The issue at hand only occurs on Android 9.0+, and relates to a foregrounded app trying to start a service, and the system not recognising that the app is in the foreground. The question you linked to deals with a backgrounded app trying to start a service on Android 8.0+ - something which is documented and not a platform bug. – Tim Malseed Oct 08 '18 at 23:07
  • try changing `contentWrapper` to `this` ie. `startService(new Intent(this, MusicService.class));` your context might be wrong – Pierre Oct 17 '18 at 06:00
  • I got the same problem with Oneplus on Android 9. But not Pixel on Android 9. – Majkeee Oct 17 '18 at 15:19
  • @Pierre I'm not sure what you mean by that. the `ContextWrapper` just wraps the Activity's `Context`. I'm not sure how that could be 'wrong'? – Tim Malseed Oct 21 '18 at 23:39
  • @TimMalseed the `Activity` you are talking about is not necessarily "alive" anymore. It could be in `Finishing` state. You can check by ((Activity)ContextWrapper).IsFinishing or IsDestroyed or IsRestricted. So it is best to use the current Context if you have it available such as `this`. It could be that you have the same activity started with flags such as `FLAG_ACTIVITY_NEW_TASK`, the same activity can exist multiple times at the same time. – Pierre Oct 22 '18 at 05:12
  • I thought that maybe the issue was targetting 27 on a 28 device, but that does not appear to fix it. – Ben987654 Oct 22 '18 at 19:04
  • @Pierre in this case, the 'wrapped context' _is_ the current context. I've edited the question as `startService(new Intent(this, MusicService.class))` to avoid this confusion. I have a single Activity in this app, and it's certainly not _finishing_ when `onResume()` has just been called. This issue looks like a framework bug (see the first comment in this thread) – Tim Malseed Oct 30 '18 at 23:31
  • @TimMalseed I hope you find a workaround for this, I can not replicate this issue. Can you try adding a `mainapplication` class extending to `android.app.Application` and implement `Application.ActivityLifecycleCallbacks` from https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks maybe it will force the platform to recognize that the activity is in foreground? – Pierre Oct 31 '18 at 05:32
  • Thanks @Pierre. I think I'll just wait for Google to address this bug. It's seems that lifecycle events are not a reliable way to determine whether the platform thinks your app is foregrounded in Android 9.0. – Tim Malseed Oct 31 '18 at 22:49
  • One thing we're testing that looks promising but is a total hack is just delaying the start service by a couple hundred milliseconds. Just need to ensure it doesn't trigger if the activity resumes and then immediately goes into the background. In our case, the delay wouldn't impact the apps functionality. – Ben987654 Nov 09 '18 at 20:34
  • @Ben987654 Did your delay resolve this? – iaindownie Jan 07 '19 at 14:24
  • 3
    @iaindownie Yes, it appears to have worked, it's been in prod for a short while now, and new instances that I hadn't fixed (didn't know about at the time) started showing up since it made it further along. – Ben987654 Jan 07 '19 at 17:13

8 Answers8

17

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);
Mateusz Kaflowski
  • 2,221
  • 1
  • 29
  • 35
  • 4
    As said in issuetracker: "activities would be paused immediately and eventually be resumed again after its fully awake." so acitivity will be resumed again and process importance will be IMPORTANCE_FOREGROUND I think. so don't need to handler. is it true? – David Apr 13 '19 at 06:19
  • 3
    Above 'hack' worked for me (note: all crashes reported for this issue in my case were on Samsung platforms, running Android 9+). For completeness, add the following line above the 'hack' to define the activityManager: "ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);" – gOnZo Sep 06 '19 at 14:16
  • @David Did you try it without the handler? – Xi Wei Jun 17 '20 at 15:48
  • @XiWei Yes, I tested it and it's ok. Just use startService after check importance in onResume. – David Jun 18 '20 at 06:39
6

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.

Ben987654
  • 3,280
  • 4
  • 27
  • 50
  • 1
    For the Oreo+ case, would this be equivalent to something like: `handler.postDelayed({context.startService(intent)}, 300)` and in the `stopped()`, calling `handler.removeCallbacksAndMessages(null)` ? – Carmen Feb 14 '19 at 09:00
  • 1
    I believe that would function the same yes. Can always test it by adding some extra logging and set the time to a second or two the confirm the delay / proper removal and ability to trigger as you open/close the app repeatedly. – Ben987654 Feb 14 '19 at 16:00
  • Can anyone confirm non Rx version works posted above using handler.postDelayed – Rinav Jun 17 '19 at 10:57
  • @rinav I would look at using googles official recommended solution at this point. I've taken out the lifecycle logic above and put their fix in the else branch instead of the Rx and it does prevent crashes. The only question is, is it completely reliable or not, which I don't know yet but we'll find out once we do a release soon. The Rx version above while it drastically reduced crashes, I still got 3-4 a month by the time I replaced every call, instead of 400-500. The good news though, is routing al the calls through this one file made it easy to swap fixes! – Ben987654 Jun 17 '19 at 15:53
5

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.

Tim Malseed
  • 6,003
  • 6
  • 48
  • 66
5

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!

2

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

Wasky
  • 573
  • 3
  • 9
0

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.

m0bl
  • 562
  • 5
  • 9
0

I've come to a solution, where I use process scope and make sure to not include scope cancellation exception in the logging logic. Like so:

with(ProcessLifecycleOwner.get()) {
  lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
      try {
        context.startService(context, Service::class.java)
      } catch (ex: CancellationException) {
        // app minimized, scope cancelled, do not log as error
      } catch (ex: IllegalStateException) {
        logToFirebase(ex)
      }
    }
  }
}

More detailed in this article https://medium.com/@lepicekmichal/android-background-service-without-hiccup-501e4479110f

Majkeee
  • 960
  • 1
  • 8
  • 28
-3

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.

Community
  • 1
  • 1
MarcinLe
  • 110
  • 1
  • 7
  • 1
    The lifecycle observer is just responding to events triggered by the activity. So this all really amounts to just moving the `startService()` call to `onStart()`. As you say though, this might be a handy workaround. – Tim Malseed Oct 21 '18 at 23:36
  • 1
    Unfortunately I am seeing the same exception with `Lifecycle.State.STARTED`. I am going to try `Lifecycle.State.RESUMED` and `@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)` which is called after `onResume()` and see if that fixes the issue. – MarcinLe Oct 22 '18 at 18:05
  • Thanks for sharing this workaround. Did you have any positive results when using Lifecycle.State.RESUMED and @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)? – Renso Lohuis Oct 29 '18 at 10:47
  • I updated my app to use State.RESUMED. For a few days it was working fine, so I hoped this solves the issue, but I have just noticed another crash on android 9 again :( – MarcinLe Oct 30 '18 at 11:41
  • Yeah, not surprised at all. Again, Arch Lifecycle events are just triggered by the lifecycle events of the underlying Fragment/Activity, so this is no different to just moving the `startService` call into `onStart()` or `onResume()` or wherever, unless you account for very minuscule timing delays. Sounds like moving the call to `onStart()` is _not_ a workaround. – Tim Malseed Oct 30 '18 at 23:26
  • We implemented this and we still see about the same number of crashes. Actually we now prevent this crash with a try/catch and log an event to fabric answers with some details. We see about the same frequency of fabric answers events as we saw with crashes. In the fabric answers details, we include the lifecycle state of the process lifecycle owner. It’s always resumed. I think we’ll try to add a 300ms delay as @ben987654’s answer suggests and see how that goes. – Carmen Feb 15 '19 at 06:35
  • Using ProcessLifecycleOwner and Lifecycle.Event.ON_RESUME won't work at 100% because as specified in ProcessLifecycleOwner's javadoc : ON_RESUME is called only when the first activity resume or if an activity is resumed after ON_PAUSE which is dispatched with a delay after the last activity was paused... The Googler said : "If the device hasn’t fully awake, activities would be paused immediately and eventually be resumed again after its fully awake." so you cannot be sure that the activity is "resumed again after its fully awake" is longer than the delay after which ON_PAUSE is dispatched – Kélian Apr 11 '19 at 14:39