Android Oreo: Keep started background service alive without setting it foreground (but with a notification)?
The correct way to handle media player services in Android Oreo seems to be to store state information when the media player is paused, so if it is destroyed , Pressing play will create a new media player and start from where it stopped.
My question is how to create a notification that will not be destroyed when the service that started it is destroyed. I No code is run to dismiss the notification, but when the service is destroyed, it will still be automatically fired. As you can see in my code, I can rebuild the notification onDestroy, but I don’t like the way it looks because The user can see it fired and rebuilt again.
This is my notification code:
private void buildNotification() {
Cat. d("building notification");
final MediaPlayerService context = this;
RequestBuilderrequestBuilder = Glide.with(this).asBitmap()
.load(R .mipmap.ic_launcher);
NotificationCompat.Action action;
if (mediaPlayer != null && mediaPlayer.isPlaying())
action = generateAction(R.drawable.ic_pause, "Pause");
else
action = generateAction(R.drawable.ic_play_ar row, "Play");
int[] actionsInCompact;
builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable. ic_navbooks_icon_black)
//.setOnlyAlertOnce(true)
.setContentTitle(book.getTitle())
.setContentText(book.getAuthor())
.setAutoCancel(false)
.setContentIntent(PendingIntentHelper.getOpenMainActivityIntent(context, book.getId()))
.setDeleteIntent(PendingIntentHelper.getStopServiceIntent(context));
if (SettingsUtil.GetNotificationSkip(context)) {
builder.addAction(R.drawable.ic_skip_backward, "Skip Previous",
PendingIntentHelper.getSkipBackwardIntent(context))
.addAction(R.drawable.ic_backward, "Rewind",
PendingIntentHelper.getSeekBackwardIntent(context))
.addAction(action)
.addAction(R.drawable.ic_forward, "Fast Forward",
PendingIntentHelper.getSeekForwardIntent(context))
.addAction(R.drawable.ic_skip_forward , "Skip Next",
PendingIntentHelper.getSkipForwardIntent(context));
actionsInCompact = new int[]{0, 1, 2, 3, 4};
} else {
builder.addAction(R.drawable.ic_backward, "Rewind",
PendingIntentHelper.getSeekBackwardIntent(context))
.addAction(action)
.addAction(R.drawable.ic_forward, "Fast Forward",
PendingIntentHelper.getSeekForwardIntent(context));
actionsInCompact = new int[]{0, 1, 2};
}
builder.setStyle(new android.support .v4.media.app.NotificationCompat.MediaStyle()
.setMediaSess ion(mediaSession.getSessionToken())
.setShowActionsInCompactView(actionsInCompact));
// Load a cover image if there isn't one loaded yet or the cover has changed
if(cover == null || !book.getCover().equals(lastCover)) {
lastCover = book.getCover();
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, book.getTitle())
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, book.getAuthor())
.build());
Glide.with(this )
.asBitmap()
.error(requestBuilder)
.load(book.getCover())
.into(new SimpleTarget() {
@Override
public void onResourceReady(Bitmap largeIcon, Transition transition) {
Cat.d("Finished loading");
cover = largeIcon;
// initBuilder
builder.setLargeIcon(largeIcon);
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, largeIcon)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, book.getTitle())
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, book.get /Author()) > .build());
startNotification();
}
});
} else {
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, book.getTitle())
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, book.getAuthor())
.build());
if(cover != null )
builder.setLargeIcon(cover);
}
startNotification();
}
private NotificationCompat.Action generateAction(int icon , String title) {
return new NotificationCompat.Action.Builder( icon, title, PendingIntentHelper.getPlayPauseIntent(this)).build();
}
private void startNotification( ) {
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(mediaPlayer.isPlaying()? PlaybackStateCompat.STATE_PLAYING :
PlaybackStateCompat.STATE_PAUSED, book.getLastPosition(), 1)
.build());
if(mediaPlayer.isPlaying())
startForeground(NOTIFICATION_ID, builder.build());
else
{
NotificationManager notificationManager =
(NotificationManager)getSystemService(NOTIFICATION_SERVICE );
if(notificationManager != null) {
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
stopForeground(false);
}
}
The following is what happens when the service is destroyed:
@Override
public void onDestroy() {
//super.onDestroy();
handleDestroy();
}
private void handleDestroy() {
if(book != null)
sendProgressUpdate();
else
book = new BookDBHelper(this).getBook(SettingsUtil.GetLastPlayedBook(this));
if(mediaPlayer != null) {
book. setLastPosition(this, getPosition());
SyncHelper.SendProg ressUpdate(this, book);
}
if(mediaSession != null)
mediaSession.release();
if(noisyRegistered) {
unregisterReceiver(becomingNoisyReceiver);
unregisterReceiver(shakeReceiver);
}
buildNotification();
}
private void startNotification() {
int NOTIFICATION_ID = 7853154;
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(mediaPlayer.isPlaying()? PlaybackStateCompat.STATE_PLAYING :
PlaybackStateCompat.STATE_PAUSED, book.getLastPosition(), 1)
.build());
if(mediaPl ayer.isPlaying())
startForeground(NOTIFICATION_ID, builder.build());
else
{
stopForeground(STOP_FOREGROUND_DETACH);
NotificationManager notificationManager =
(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
if(notificationManager != null) {
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
}
}
Okay. I have a question about keeping the media player service in Android Oreo. According to the discussion here:
< p>Android Oreo: Keep started background service alive without setting it foreground (but with a notification)?
The correct way to handle media player services in Android Oreo seems to be to store state when the media player is paused Information, so if it is destroyed, pressing play will create a new media player and start from where it stopped.
My question is how to create a notification when the service that started it is destroyed It will not be destroyed when I am not running any code to dismiss the notification, but when the service is destroyed, it will still be automatically fired. As you can see in my code, I can rebuild the notification onDestroy, but I don’t like it The way it looks, because the user can see it fired and rebuilt again.
This is my notification code:
private void buildNotification() {
Cat.d("building notification");
final Me diaPlayerService context = this;
RequestBuilderrequestBuilder = Glide.with(this).asBitmap()
.load(R.mipmap.ic_launcher);
NotificationCompat.Action action;
if (mediaPlayer != null && mediaPlayer.isPlaying())
action = generateAction(R.drawable.ic_pause, "Pause");
else
action = generateAction(R.drawable.ic_play_arrow, "Play");
int[] actionsInCompact;
builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_navbooks_icon_black)
//.setOnlyAlertOnce(true)
.setContentTitle(book.getTitle())
.setContentText(book.getAuthor())
.setAutoCancel(false)
.setContentIntent(PendingIntentHelper.getOpenMainActivityIntent(context, book.getId()))
.setDelet eIntent(PendingIntentHelper.getStopServiceIntent(context));
if (SettingsUtil.GetNotificationSkip(context)) {
builder.addAction(R.drawable.ic_skip_backward, "Skip Previous",
PendingIntentHelper.getSkipBackwardIntent(context))
.addAction(R.drawable.ic_backward, "Rewind",
PendingIntentHelper.getSeekBackwardIntent(context))
.addAction(action)
. addAction(R.drawable.ic_forward, "Fast Forward",
PendingIntentHelper.getSeekForwardIntent(context))
.addAction(R.drawable.ic_skip_forward, "Skip Next",
PendingIntentHelper.getSkipForwardIntent( context));
actionsInCompact = new int[]{0, 1, 2, 3, 4};
} else {
builder.addAction(R.drawable.ic_backward, "Rewind" ,
PendingIntentHel per.getSeekBackwardIntent(context))
.addAction(action)
.addAction(R.drawable.ic_forward, "Fast Forward",
PendingIntentHelper.getSeekForwardIntent(context));
actionsInCompact = new int[]{0, 1, 2};
}
builder.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setMediaSession( mediaSession.getSessionToken())
.setShowActionsInCompactView(actionsInCompact));
// Load a cover image if there isn't one loaded yet or the cover has changed
if( cover == null || !book.getCover().equals(lastCover)) {
lastCover = book.getCover();
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
. putString(MediaMetadataCompat.METADATA_KEY_ARTIST, book.getTitle())
.putString(MediaMetadataCompat.METADATA_KEY_TI TLE, book.getAuthor())
.build());
Glide.with(this)
.asBitmap()
.error(requestBuilder)
. load(book.getCover())
.into(new SimpleTarget() {
@Override
public void onResourceReady(Bitmap largeIcon, Transition transition) {
Cat. d("Finished loading");
cover = largeIcon;
// initBuilder
builder.setLargeIcon(largeIcon);
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, largeIcon)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, book.getTitle())
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, book.getAuthor())
.build());
startNotification();
}
});
} else {
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, book.getTitle())
.putString (MediaMetadataCompat.METADATA_KEY_TITLE, book.getAuthor())
.build());
if(cover != null)
builder.setLargeIcon(cover);
}
startNotification();
}
private NotificationCompat.Action generateAction(int icon, String title) {
return new NotificationCompat.Action.Builder( icon, title, PendingIntentHelper.getPlayPauseIntent(thi s)).build();
}
private void startNotification() {
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(mediaPlayer.isPlaying()? PlaybackStateCompat.STATE_PLAYING :
PlaybackStateCompat.STATE_PAUSED, book.getLastPosition(), 1)
.build());
if(mediaPlayer.isPlaying())
startForeground(NOTIFICATION_ID, builder.build());
else
{
NotificationManager notificationManager =
(NotificationManager)getSystemService( NOTIFICATION_SERVICE);
if(notificationManager != null) {
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
stopForeground(false);
}
}
The following is what happens when the service is destroyed:
@Override
public voi d onDestroy() {
//super.onDestroy();
handleDestroy();
}
private void handleDestroy() {
if( book != null)
sendProgressUpdate();
else
book = new BookDBHelper(this).getBook(SettingsUtil.GetLastPlayedBook(this));
if(mediaPlayer != null ) {
book.setLastPosition(this, getPosition());
SyncHelper.SendProgressUpdate(this, book);
}
if(mediaSession != null)
mediaSession.release();
if(noisyRegistered) {
unregisterReceiver(becomingNoisyReceiver);
unregisterReceiver(shakeReceiver);
}
buildNotification();
}
Okay. I found that the stopForeground function has a flag optional parameter instead of a boolean value. You can give it a flag STOP_FOREGROUND_DETACH, even if After calling stopForeground, you also need to separate the notification from the service, so that when the service is destroyed, the notification will not be dismissed, and there is no need to build a new notification. This is my updated start notification code:
private void startNotification() {
int NOTIFICATION_ID = 7853154;
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setActions(MEDIA_SESSION_ACTIONS)
.setState(mediaPlayer.isPlaying()? PlaybackStateCompat.STATE_PLAYING :
PlaybackStateCompat .STATE_PAUSED, book.getLastPosition(), 1)
.build());
if(mediaPlayer.isPlaying())
startForeground(NOTIFICATION_ID, builder.build() );
else
{
stopForeground(STOP_FOREGROUND_DETACH);
NotificationManager notificationManager =
(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
if(notificationManager != null ) {
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
}
}