android - 在 Oreo 中销毁服务时防止通知被取消

标签 android service notifications media-player

好的。我有一个关于在 Android Oreo 中保留媒体播放器服务的问题。基于此处的讨论:

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

在 Android Oreo 中处理媒体播放器服务的正确方法似乎是在媒体播放器暂停时存储状态信息,因此如果它被破坏,按下播放将创建一个新的媒体播放器并从它停止的地方开始。

我的问题是如何创建一个在启动它的服务被销毁时不会被销毁的通知。我没有运行任何代码来取消通知,但当服务被销毁时它仍然被自动取消。正如您在我的代码中看到的那样,我可以重建 onDestroy 通知,但我不喜欢这种方式,因为用户可以看到它被关闭并再次重建。

这是我的通知代码:

private void buildNotification() {
        Cat.d("building notification");
        final MediaPlayerService context = this;

        RequestBuilder<Bitmap> requestBuilder = 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()))
                .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()
                .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_TITLE, book.getAuthor())
                    .build());
            Glide.with(this)
                    .asBitmap()
                    .error(requestBuilder)
                    .load(book.getCover())
                    .into(new SimpleTarget<Bitmap>() {
                        @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(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);
        }
    }

这是服务被销毁时发生的情况:

@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.SendProgressUpdate(this, book);
        }
        if(mediaSession != null)
            mediaSession.release();

        if(noisyRegistered) {
            unregisterReceiver(becomingNoisyReceiver);
            unregisterReceiver(shakeReceiver);
        }
        buildNotification();
    }

最佳答案

好的。我发现 stopForeground 函数有一个可选的标志参数而不是 bool 值。它可以被赋予一个标志 STOP_FOREGROUND_DETACH ,即使在调用 stopForeground 之后,它也是从服务中分离通知所必需的,这样当服务被销毁时,通知不会被取消并且不必构建新的通知。这是我用于启动通知的更新代码:

private fun updateNotification(preparing: Boolean = false) {
    if(getPlaying()) { 
        startService(Intent(applicationContext, this@MediaPlayerService.javaClass))
        startForeground(NOTIFICATION_ID, getNotification())
    }else {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
            stopForeground(Service.STOP_FOREGROUND_DETACH)
        else
            stopForeground(false)
        notificationManager.notify(NOTIFICATION_ID, getNotification())
    }
}

关于android - 在 Oreo 中销毁服务时防止通知被取消,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49100515/

相关文章:

java - 如何做好服务经理?

HTML 通知 : What to do on server-side

windows - 无法在 Windows 上将 SonarQube 5.3 作为服务运行

c# - Java Service 类,类似于 C# 的 ServiceBase

android - 错误 :Execution failed for task ':app:transformClassesWithDexForDebug' . > com.android.build.api.transform.TransformException :. .非零退出值 3

java - 我正在使用线程,当我离开 fragment 时,应用程序关闭

Android通知,振动但没有声音

java - 在自定义内容通知中使用后更新 RemoteView

java - 从启动时启动的服务访问资源

android - 将字符串转换为 LocalDateTime