Android Oreo: Keep started background service alive without setting it foreground (but with a notification)?
Asked Answered
E

0

5

I am working on a media app. I wonder how I can keep some old behavior my app has prior to Oreo, for example, have the notification and the service (for playback) hang in there even if the service is no longer set to foreground.

We call startService(MediaPlaybackService.class) to start the service when playback starts, then create a notification and call startForeground() with it on the service. So far so good - if user exits the app, user will still have media play in the background and can control the playback from the notification.

When user pauses playback, we call stopForeground(false) to make the notification dismissible. Here comes the problem, since Android Oreo, the system prevents starting non-foreground services and stops the running ones. In my case, on pausing playback, service becomes non-foreground and is subject to be stopped by system. And since it's like a proper stopSelf() call, setting onStartCommand() to return "START_STICKY" doesn't seem to help deliver another start call to the service, and most likely won't work even if it does because the app is still in background.

It looks like it's working for Spotify - their notification stays indefinitely when playback is paused; and the notification is dismissible so I figured it's not set to foreground. Wonder how it works for Spotify?

Here is some sample code:

class MyService extends MediaBrowserServiceCompat implements PlayerStateListener {

  public int onStartCommand(...) {
    //....
    return START_STICKY;
  }

  void onPlaybackStarts() {
    makeServiceStart(); // handles before and after 26
    Notification notif = buildNotif();
    startForeground(NOTIF_ID, notif);  // non-zero value
  }

  void onPlaybackStops() {
    // if app is in background at this point, system will stop the service short after the line below.
    stopForeground(false);
    if (state == Player.IDLE) {
      stopSelf();
    }
  }
}
Ernie answered 9/1, 2018 at 23:32 Comment(8)
Are you using MediaButtonReceiver.buildMediaButtonPendingIntent() for your notification actions? That works regardless of whether your Service is already started or not assuming your onPlaybackStarts() / onPlaybackStops() is called from onPlay()/onStop() as per the callbacks guide.Cown
"In my case, on pausing playback, service becomes non-foreground and is subject to be stopped by system" -- what is the problem with this? Your Notification can then start the service via a getForegroundService() PendingIntent, if the user taps the action (or whatever) you have in that Notification to resume playback.Remaremain
In other words user interaction with notification (pressing play button) can start foreground service again.Impulsive
@Remaremain Thanks for the reply! Now I realized that it could totally be the case for the apps that have this functionality working. Our service carries state so I have been struggling keeping it alive. The way we architected our app, mediaSession is not the only/main source of truth, and on service.onDestroy() we do a bunch of cleanup, e.g. cancel notification and release media session. hmmm let me try letting the service go and not releasing mediaSession on service destroy.. what's the suggested practice on keeping media session around? thank you thank you!Ernie
"Our service carries state so I have been struggling keeping it alive" -- persist the relevant bits of state (e.g., track, position within the track) when you pause playback, so that you can restore that state if your process is terminated. "what's the suggested practice on keeping media session around?" -- sorry, that's outside of my area of expertise.Remaremain
@Cown thanks for the reply, Ian! I haven't gotten to the part to see whether the notification controls work or not, as I mentioned in the comment above, we cancel notifications on service.onDestroy(). I checked the code though and we are not currently using MediaButtonReceiver.buildMediaButtonPendingIntent(). Just to help me understand why we need it - what does it mean it "works" regardless the service is started? And would PendingIntent.getBroadcast(...) not work? Thank you!!Ernie
Per the callbacks guide I linked, you shouldn't be canceling the notification (although on onDestroy() you should still be releasing your MediaSessionCompat). See the source of MediaButtonReceiver - it connects via MediaBrowserCompat, binding to your MediaBrowserServiceCompat. That temporarily will create your Service if it isn't already running, allowing you to handle the media button.Cown
@Cown got it! In my specific case we cancel notification on service destroy because we had the assumption that service would rarely get killed given our architecture (started on create, bound by every activity). However this is no longer true now with Android Oreo and we will need to make some adjustments. And thanks for the note on MediaBrowserCompat! I think it helps explained another crash we started to see since Android Oreo but haven't been able to reproduce! mediaButton event from car or something -> bind_auto_create service -> startService on service create -> crashErnie

© 2022 - 2024 — McMap. All rights reserved.