Como atualizo o texto da notificação para um serviço em primeiro plano no Android?

133

Eu tenho uma configuração de serviço em primeiro plano no Android. Gostaria de atualizar o texto da notificação. Estou criando o serviço, como mostrado abaixo.

Como posso atualizar o texto de notificação configurado neste serviço em primeiro plano? Qual é a melhor prática para atualizar a notificação? Qualquer código de exemplo seria apreciado.

public class NotificationService extends Service {

    private static final int ONGOING_NOTIFICATION = 1;

    private Notification notification;

    @Override
    public void onCreate() {
        super.onCreate();

        this.notification = new Notification(R.drawable.statusbar, getText(R.string.app_name), System.currentTimeMillis());
        Intent notificationIntent = new Intent(this, AbList.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        this.notification.setLatestEventInfo(this, getText(R.string.app_name), "Update This Text", pendingIntent);

        startForeground(ONGOING_NOTIFICATION, this.notification);

    }

Estou criando o serviço na minha atividade principal, como mostrado abaixo:

    // Start Notification Service
    Intent serviceIntent = new Intent(this, NotificationService.class);
    startService(serviceIntent);
Lucas
fonte

Respostas:

61

Eu acho que chamar startForeground()novamente com o mesmo ID exclusivo e um Notificationcom as novas informações funcionaria, embora eu não tenha tentado esse cenário.

Atualização: com base nos comentários, você deve usar o NotifcationManager para atualizar a notificação e seu serviço continua no modo de primeiro plano. Dê uma olhada na resposta abaixo.

CommonsWare
fonte
1
Você poderia me dar um exemplo de como eu chamaria isso da minha Atividade? Não consegui encontrar uma boa amostra de como chamar métodos no meu serviço em primeiro plano.
Lucas
1
@ Lucas: Há vários padrões para usar um serviço, e eu não tenho ideia do que o seu está seguindo. Se você estiver ligando startService()para passar um comando para o serviço, basta ligar startService()novamente para solicitar a atualização do texto. Ou, se você estiver chamando bindService(), adicione um método à sua API para que o serviço atualize seu texto. Ou considere se o serviço em si deve ser quem decide se deve ou não atualizar o texto. Ou talvez o texto seja SharedPeferenceaquele em que o serviço tenha um ouvinte. É impossível fornecer conselhos precisos em resumo.
CommonsWare
9
para esclarecer mais: você não pode cancel()definir uma Notificação por startForeground(). Você tem que remover o status de primeiro plano da própria (usando o serviço stopForeground()se você quiser fazer o texto ticker aparecer novamente eu perdi horas porque estas respostas me levou a acreditar que era na verdade possível..
slinden77
4
Eu diminuí a votação desta resposta, pois ela está claramente errada: developer.android.com/training/notify-user/managing.html Por favor, @CommonsWare considere remover essa resposta, pois sua pontuação de alta reputação faz com que essa resposta seja a "verdade sagrada" para o navegador casual. Obrigado.
HYS
2
Não funcionou para mim (embora eu me lembre de usar esse mesmo método em um projeto anterior). Usando NotificationManagerfuncionou como eu esperava.
user149408
224

Quando você quiser atualizar um conjunto de Notificações por startForeground (), simplesmente crie uma nova observação e use o NotificationManager para notificá-la.

O ponto principal é usar o mesmo ID de notificação.

Não testei o cenário de chamar repetidamente startForeground () para atualizar a Notificação, mas acho que usar o NotificationManager.notify seria melhor.

A atualização da notificação NÃO removerá o serviço do status de primeiro plano (isso pode ser feito apenas chamando stopForground);

Exemplo:

private static final int NOTIF_ID=1;

@Override
public void onCreate (){
    this.startForeground();
}

private void startForeground() {
    startForeground(NOTIF_ID, getMyActivityNotification(""));
}

private Notification getMyActivityNotification(String text){
    // The PendingIntent to launch our activity if the user selects
    // this notification
    CharSequence title = getText(R.string.title_activity);
    PendingIntent contentIntent = PendingIntent.getActivity(this,
            0, new Intent(this, MyActivity.class), 0);

    return new Notification.Builder(this)
            .setContentTitle(title)
            .setContentText(text)
            .setSmallIcon(R.drawable.ic_launcher_b3)
            .setContentIntent(contentIntent).getNotification();     
}

/**
 * This is the method that can be called to update the Notification
 */
private void updateNotification() {
    String text = "Some text that will update the notification";

    Notification notification = getMyActivityNotification(text);

    NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    mNotificationManager.notify(NOTIF_ID, notification);
}

A documentação declara

Para configurar uma notificação para que possa ser atualizada, emita-a com um ID de notificação ligando para NotificationManager.notify(). Para atualizar esta notificação após a sua emissão, atualize ou crie um NotificationCompat.Builderobjeto, crie um Notificationobjeto a partir dele e emita Notificationo mesmo com o mesmo ID usado anteriormente. Se a notificação anterior ainda estiver visível, o sistema a atualizará do conteúdo do Notificationobjeto. Se a notificação anterior foi descartada, uma nova notificação será criada.

Luca Manzo
fonte
35
ESSA É A RESPOSTA CORRETA! A resposta acima é muito errada e enganosa. Você não precisa reiniciar o serviço apenas para atualizar uma notificação boba.
Radu
7
@Radu Embora eu concorde que essa é a resposta ideal (evita o caminho de código um pouco mais longo adotado pela resposta do Commons), você está enganado sobre o que a resposta do Commons faz - start / stopForegound não inicia / interrompe o serviço, apenas afeta seu primeiro plano .
Stevie
@ Stevie Obrigado por Stevie, você provavelmente está certo. Ainda assim, eu não iria mexer com isso também!
Radu
Chamar o notify()ou os startForeground()dois leva à chamada onStartCommand().
M. Reza Nasirloo
10
O problema de usar NotificationManagerpara atualizar uma notificação mostrada startForegroundé que a chamada stopForegroundnão removerá mais a notificação. Atualizá-lo com outra chamada para startForegroundevitar esse problema.
Tad
21

Ao melhorar a resposta de Luca Manzo no Android 8.0+, ao atualizar a notificação, ele emitirá som e será exibido como Heads-up.
para impedir que você precise adicionarsetOnlyAlertOnce(true)

então o código é:

private static final int NOTIF_ID=1;

@Override
public void onCreate(){
        this.startForeground();
}

private void startForeground(){
        startForeground(NOTIF_ID,getMyActivityNotification(""));
}

private Notification getMyActivityNotification(String text){
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
        ((NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(
        NotificationChannel("timer_notification","Timer Notification",NotificationManager.IMPORTANCE_HIGH))
}

        // The PendingIntent to launch our activity if the user selects
        // this notification
        PendingIntent contentIntent=PendingIntent.getActivity(this,
        0,new Intent(this,MyActivity.class),0);

        return new NotificationCompat.Builder(this,"my_channel_01")
        .setContentTitle("some title")
        .setContentText(text)
        .setOnlyAlertOnce(true) // so when data is updated don't make sound and alert in android 8.0+
        .setOngoing(true)
        .setSmallIcon(R.drawable.ic_launcher_b3)
        .setContentIntent(contentIntent)
        .build();
}

/**
 * This is the method that can be called to update the Notification
 */
private void updateNotification(){
        String text="Some text that will update the notification";

        Notification notification=getMyActivityNotification(text);

        NotificationManager mNotificationManager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(NOTIF_ID,notification);
}
humazed
fonte
Você salvou o meu dia. Obrigado
Rubén Viguera
1
new NotificationChannel
Palavra-
5

aqui está o código para fazê-lo em seu serviço . Crie uma nova notificação, mas solicite ao gerente de notificações para notificar o mesmo ID de notificação usado no startForeground.

Notification notify = createNotification();
final NotificationManager notificationManager = (NotificationManager) getApplicationContext()
    .getSystemService(getApplicationContext().NOTIFICATION_SERVICE);

notificationManager.notify(ONGOING_NOTIFICATION, notify);

para obter códigos de amostra completos, você pode conferir aqui:

https://github.com/plateaukao/AutoScreenOnOff/blob/master/src/com/danielkao/autoscreenonoff/SensorMonitorService.java

Daniel Kao
fonte
Não tenho certeza se isso manterá o status de primeiro plano de startService.
Martin Marconcini
@ Daniel Kao Sua solução não inicia um serviço de primeiro plano
IgorGanapolsky
4
Corrija-me se eu estiver errado, mas as pessoas que votaram nessa resposta podem ser mais descritivas quanto ao que há de errado com ela? A pergunta não pergunta como iniciar um serviço em primeiro plano, mas como atualizar uma notificação de um serviço em primeiro plano. Esta é efetivamente a mesma resposta que Luca, que as pessoas concordam que funciona e mantém o status de primeiro plano.
TheIT
@TheIT Não funciona. O status da notificação se torna not foregroundpara foreground createdmensagem.
Vyacheslav
1
Isso resultaria em uma notificação duplicada, porque startForeground () já foi chamado.
IgorGanapolsky
2

Parece que nenhuma das respostas existentes mostra como lidar com o caso completo - para iniciar o Foreground se for a primeira chamada, mas atualizar a notificação para as chamadas subseqüentes.

Você pode usar o seguinte padrão para detectar o caso correto:

private void notify(@NonNull String action) {
    boolean isForegroundNotificationVisible = false;
    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    StatusBarNotification[] notifications = notificationManager.getActiveNotifications();
    for (StatusBarNotification notification : notifications) {
        if (notification.getId() == FOREGROUND_NOTE_ID) {
            isForegroundNotificationVisible = true;
            break;
        }
    }
    Log.v(getClass().getSimpleName(), "Is foreground visible: " + isForegroundNotificationVisible);
    if (isForegroundNotificationVisible){
        notificationManager.notify(FOREGROUND_NOTE_ID, buildForegroundNotification(action));
    } else {
        startForeground(FOREGROUND_NOTE_ID, buildForegroundNotification(action));
    }
}

Além disso, você precisa criar a notificação e o canal como em outras respostas:

private Notification buildForegroundNotification(@NonNull String action) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        createNotificationChannel();
    }
    //Do any customization you want here
    String title;
    if (ACTION_STOP.equals(action)) {
        title = getString(R.string.fg_notitifcation_title_stopping);
    } else {
        title = getString(R.string.fg_notitifcation_title_starting);
    }
    //then build the notification
    return new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle(title)
            .setOngoing(true)
            .build();
}

@RequiresApi(Build.VERSION_CODES.O)
private void createNotificationChannel(){
    NotificationChannel chan = new NotificationChannel(CHANNEL_ID, getString(R.string.fg_notification_channel), NotificationManager.IMPORTANCE_DEFAULT);
    chan.setLightColor(Color.RED);
    chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
    NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    assert manager != null;
    manager.createNotificationChannel(chan);
}
Nick Cardoso
fonte