Разрешить отмену уведомления после вызова stopForeground (false)

У меня есть служба мультимедиа, которая использует startForeground () для отображения уведомления при запуске воспроизведения. У него есть кнопки паузы / остановки во время воспроизведения, кнопки воспроизведения / остановки во время паузы.

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
// setup...

Notification n = mBuilder.build();

if (state == State.Playing) {
    startForeground(mId, n);
}
else {
    stopForeground(false);
    mNotificationManager.notify(mId, n);        
}

Проблема здесь в том, что когда я показываю / обновляю уведомление в приостановленном состоянии, вам должно быть разрешено его удалить. mBuilder.setOngoing(false), похоже, не имеет никакого эффекта, поскольку предыдущий startForeground отменяет его.

Вызов stopForeground(true); с тем же кодом работает должным образом, но уведомление мигает, поскольку оно уничтожается и создается заново. Есть ли способ «обновить» уведомление, созданное из startForeground, чтобы его можно было удалить после вызова stop?

Изменить: Вот полный код, создающий уведомление, согласно запросу. createNotification вызывается всякий раз, когда служба воспроизводится или приостанавливается.

private void createNotification() {
    NotificationCompat.Builder mBuilder = 
            new NotificationCompat.Builder(this)
    .setSmallIcon(R.drawable.ic_launcher)
    .setContentTitle("No Agenda")
    .setContentText("Live stream");

    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
    {
        if (state == State.Playing) {
            Intent pauseIntent = new Intent(this, MusicService.class);
            pauseIntent.setAction(ACTION_PAUSE);
            PendingIntent pausePendingIntent =     PendingIntent.getService(MusicService.this, 0, pauseIntent, 0);              

            mBuilder.addAction(R.drawable.pause, "Pause", pausePendingIntent);
            //mBuilder.setOngoing(true);
        }
        else if (state == State.Paused) {
            Intent pauseIntent = new Intent(this, MusicService.class);
            pauseIntent.setAction(ACTION_PAUSE);
            PendingIntent pausePendingIntent =  PendingIntent.getService(MusicService.this, 0, pauseIntent, 0);

            mBuilder.addAction(R.drawable.play, "Play", pausePendingIntent);
            mBuilder.setOngoing(false);
        }

        Intent stopIntent = new Intent(this, MusicService.class);
        stopIntent.setAction(ACTION_STOP);
        PendingIntent stopPendingIntent = PendingIntent.getService(MusicService.this, 0, stopIntent, 0);

        setNotificationPendingIntent(mBuilder);
        mBuilder.addAction(R.drawable.stop, "Stop", stopPendingIntent);
    }
    else
    {
        Intent resultIntent = new Intent(this, MainActivity.class);
        PendingIntent intent = PendingIntent.getActivity(this, 0, resultIntent, 0);

        mBuilder.setContentIntent(intent);
    }

    Notification n = mBuilder.build();

    if (state == State.Playing) {
        startForeground(mId, n);
    }
    else {
        stopForeground(true);
        mNotificationManager.notify(mId, n);
    }
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setNotificationPendingIntent(NotificationCompat.Builder mBuilder) {
    Intent resultIntent = new Intent(this, MainActivity.class);

    TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
    stackBuilder.addParentStack(MainActivity.class);
    stackBuilder.addNextIntent(resultIntent);

    PendingIntent resultPendingIntent =
            stackBuilder.getPendingIntent(
                0,
                PendingIntent.FLAG_UPDATE_CURRENT
            );

    mBuilder.setContentIntent(resultPendingIntent);
}

Последующее изменение:

В одном из комментариев ниже упоминается, что ответ может быть «хрупким», и с момента выпуска Android 4.3 поведение startForeground изменилось. startForeground заставит ваше приложение отображать уведомление, находясь на переднем плане, и метод должен просто вызываться с отображаемым уведомлением. Я не тестировал, но принятый ответ может больше не работать должным образом.

В плане остановки перепрошивки при вызове stopForeground бороться за фреймворк не стоит.

Есть некоторые дополнительную информацию об изменении уведомлений Android 4.3 здесь.


person Will Eddins    schedule 25.03.2013    source источник
comment
Можете ли вы показать свой код при попытке позвонить setOngoing(false)? Я подозреваю, что уведомление не перестраивается, чтобы принять изменения, но по этому фрагменту трудно сказать.   -  person dsandler    schedule 25.03.2013
comment
@dsandler Я добавил полный код для createNotification ().   -  person Will Eddins    schedule 26.03.2013
comment
Я также хотел бы отметить, что на данный момент я тестирую только Jellybean, в настоящее время 4.2.2 (эмулятор и телефон), где я устанавливаю setOngoing (false). Кроме того, setOngoing (false) работает, когда уведомление не запускается через startForeground (а setOngoing (true) раскомментирован).   -  person Will Eddins    schedule 26.03.2013


Ответы (4)


Вы можете рассмотреть возможность использования другого подхода.
Поскольку вы должны использовать службу переднего плана для такой задачи (воспроизведение мультимедиа), я предлагаю вам продолжать делать start foreground(), но вместо того, чтобы передавать ему уведомление, просто установите идентификатор 0 и нулевое уведомление, как это startForeground(0, null);.

Таким образом, служба переднего плана не будет отображать никаких уведомлений.

Теперь для ваших целей вы можете использовать регулярные уведомления и обновлять их состояния (текущие, макет, текст и т. Д.), Таким образом вы не зависите от поведения уведомлений службы переднего плана.

Надеюсь это поможет.

person Inon Stelman    schedule 02.04.2013
comment
Это похоже на правильный путь, однако передача нулевого уведомления вызывает исключение IllegalArgumentException в startForeground (). Следующий вопрос, похоже, указывает на то же самое: stackoverflow.com/questions/10962418 / - person Will Eddins; 09.04.2013
comment
Это интересно, потому что я использовал этот метод пару раз и просто повторно протестировал его с пустым проектом. Я также убедился, что служба действительно работает на переднем плане, запросив PackageManager. Вы также можете попробовать передать 0 в качестве идентификатора в startForeground(id, notification) - person Inon Stelman; 09.04.2013
comment
Использование 0 в качестве идентификатора сработало, и все работает, как ожидалось. Любой идентификатор, отличный от 0, кажется, вызывает исключение IllegalArgumentException. Идеально! - person Will Eddins; 10.04.2013
comment
Кажется довольно хрупким. Мне кажется очевидным, что Google хочет, чтобы было уведомление для служб переднего плана, поэтому я бы не стал рассчитывать на то, что текущее поведение будет работать в будущих версиях Android ... Я предлагаю использовать stopForeground (true) и жить со вспышкой уведомлений. - person jonasb; 10.04.2013
comment
@WillEddins, не могли бы вы рассказать мне, как вы использовали переключатель в текущем уведомлении. На самом деле я не могу это понять. Я застрял за последние 2 дня. Это будет большим подспорьем. - person Sanghati Mukherjee; 18.07.2013
comment
@SanghatiMukherjee Вы действительно каждый раз создаете новое уведомление с другим действием. Это просто похоже на переключатель для конечного пользователя. - person Will Eddins; 18.07.2013
comment
@jonasb @inistel Поскольку jonasb изначально заявил, что этот ответ может быть «хрупким», он может больше не работать с Android 4.3, поскольку startForeground теперь создает собственное уведомление, если оно не было передано, чтобы попытаться помешать мошенническим приложениям злоупотреблять передним планом статус. См. Правку внизу моего вопроса для получения дополнительной информации. - person Will Eddins; 30.07.2013
comment
Это больше не работает с версии 4.3 (или, по крайней мере, служба по-прежнему будет отображать уведомление), как указано здесь: commonsware.com/blog/2013/07/30/ - person Richard Fung; 24.06.2014

Вместо stopService(true), вызов stopService(false) сохранит уведомление как есть (без текущего состояния), если оно не будет отклонено пользователем / удалено программно или если служба остановится. Следовательно, просто вызовите stopService (false) и обновите уведомление, чтобы показать приостановленное состояние, и теперь уведомление может быть отклонено пользователем. Это также предотвращает мигание, поскольку мы не воссоздаем уведомление.

person akshay7692    schedule 05.03.2016

Согласно документам такое поведение было задумано до Lollipop.

Приложения, ориентированные на эту или более позднюю версию, получат следующие новые изменения в поведении:

...

  • Вызов Service.stopForeground с removeNotification false изменит все еще отправленное уведомление, чтобы оно больше не было принудительным.
person kreker    schedule 20.01.2015

Перед N уведомление должно появиться снова, когда stopForeground и startForeground вызываются в последовательности. Вам нужно показать уведомление еще раз и не делать его уведомлением переднего плана после паузы. Я рекомендую вам не добавлять обходные пути или взломать, чтобы исправить это (есть обходные пути, но могут появиться другие ошибки).

На N + добавлен флаг ServiceCompat.STOP_FOREGROUND_DETACH. Это будет передано в stopForeground и позволит вам сохранить текущее уведомление в панели уведомлений.

Также не забудьте убить службу (stopSelf () или что-то еще). Поскольку, если пользователь удаляет приложение, основной процесс вашего приложения может остаться.

person Xing Liu    schedule 24.09.2020