【问题标题】:Keeping notification from dismissing when service is destroyed in Oreo当奥利奥中的服务被破坏时,保持通知不会​​被解雇
【发布时间】:2020-11-24 16:24:09
【问题描述】:

好的。我有一个关于在 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();
    }

【问题讨论】:

  • “我的问题是如何创建一个在启动它的服务被破坏时不会被破坏的通知”——如果服务被破坏,Notification 有什么价值用户?
  • Android 8 会在一定时间后销毁所有不在前台运行的服务。如果我可以阻止通知被关闭,用户可以再次启动媒体播放器,从通知中中断的地方继续播放,而无需打开应用程序。
  • “Android 8 将在一定时间后销毁任何不在前台运行的服务”——您的服务应该只在播放时运行。一旦用户暂停播放,您将停止服务,直到用户选择恢复播放。 Only have a service running when it is actively delivering value to the user。就您的功能而言,请选择加入,AFAIK 您将不得不重新提出Notification 作为停止服务的一部分。
  • 我并没有试图阻止服务被破坏,并且我明白如果服务没有做任何事情,它就不应该运行。我试图弄清楚为什么通知被驳回。为了测试,我让服务启动了另一个通知,当服务被销毁时它没有被解雇NotificationCompat.Builder(this.getApplicationContext(), NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.ic_navbooks_icon_black) .setContentTitle(book.getTitle()) notificationManager.notify(24678, testNotification.build());
  • @JoelPage 如果您有任何关于在 >= oreo 中关闭通知的解决方案,请告诉我。谢谢。

标签: android service notifications media-player


【解决方案1】:

好的。我发现 stopForeground 函数有一个标志的可选参数而不是布尔值。可以给它一个标志 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())
    }
}

【讨论】:

  • 这点很好!尽管如此,这适用于 API 24 及更高版本。 API 23 及更低版本呢?似乎 stopForeground(false) 没有保留通知...
  • 我已经用我当前使用的代码更新了我的示例函数。我添加了几件事: startForeground 之前的 startService 修复了当启动它的活动关闭时通知被关闭的问题。也可以在 stopForeground(false) 之后调用 notify 来更新并保持后台通知。
  • 谢谢。快速响应乔尔。似乎有一件事没有准确地写出来。这就是自 Build.VERSION_CODES.O 以来可用的 startForeground(NOTIFICATION_ID, getNotification())
  • 我不明白这个问题。此代码在我的应用程序上运行良好,该应用程序的最小 SDK 为 21。
  • 啊,对不起。你的权利。现在不知道为什么我写了最后一个笔记。如您所说,stgartForeground 似乎已准备就绪。
猜你喜欢
  • 1970-01-01
  • 2019-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-22
相关资源
最近更新 更多