¿Cómo actualizo el texto de notificación para un servicio en primer plano en Android?

133

Tengo una configuración de servicio en primer plano en Android. Me gustaría actualizar el texto de notificación. Estoy creando el servicio como se muestra a continuación.

¿Cómo puedo actualizar el texto de notificación que se configura dentro de este servicio en primer plano? ¿Cuál es la mejor práctica para actualizar la notificación? Cualquier código de muestra sería 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);

    }

Estoy creando el servicio en mi actividad principal como se muestra a continuación:

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

Respuestas:

61

Pensaría que startForeground()volver a llamar con la misma identificación única y Notificationcon la nueva información funcionaría, aunque no he probado este escenario.

Actualización: según los comentarios, debe usar NotifcationManager para actualizar la notificación y su servicio continúa en el modo de primer plano. Echa un vistazo a la respuesta a continuación.

CommonsWare
fuente
1
Sin embargo, ¿podría darme un ejemplo de cómo llamaría eso desde mi Actividad? No he podido encontrar una buena muestra sobre cómo llamar a los métodos en mi servicio en primer plano.
Lucas
1
@Luke: Hay varios patrones para usar un servicio, y no tengo idea de cuál es el tuyo. Si llama startService()para pasar un comando al servicio, simplemente llame startService()nuevamente para indicarle que actualice su texto. O, si está llamando bindService(), agregue un método a su API para que el servicio actualice su texto. O bien, considere si el servicio en sí debería ser quien toma la decisión de actualizar o no el texto. O tal vez el texto es un tema en el SharedPeferenceque el servicio tiene un oyente. Es imposible darle consejos precisos en abstracto.
CommonsWare
9
para aclarar más: no se puede establecer cancel()una notificación por startForeground(). Debe eliminar el estado de primer plano del servicio en sí mismo ( stopForeground()si lo desea, si desea que el texto de ticker vuelva a aparecer. Perdí horas porque estas respuestas me llevaron a creer que, de hecho, era posible.
Slinden77
44
He rechazado esta respuesta, ya que es claramente errónea: developer.android.com/training/notify-user/managing.html Por favor, @CommonsWare considere eliminar esta respuesta, ya que su alto puntaje de reputación hace que esta respuesta sea la "santa verdad" para el navegador informal Gracias.
HYS
2
No funcionó para mí (aunque recuerdo haber usado este mismo método en un proyecto anterior). El uso NotificationManagerfuncionó como esperaba.
user149408
224

Cuando desee actualizar un conjunto de notificaciones por startForeground (), simplemente cree una nueva notificación y luego use NotificationManager para notificarlo.

El punto clave es usar la misma identificación de notificación.

No probé el escenario de llamar repetidamente a startForeground () para actualizar la Notificación, pero creo que usar NotificationManager.notify sería mejor.

La actualización de la Notificación NO eliminará el Servicio del estado de primer plano (esto solo se puede hacer llamando a stopForground);

Ejemplo:

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);
}

La documentación declara

Para configurar una notificación para que pueda actualizarse, emítala con un ID de notificación llamando NotificationManager.notify(). Para actualizar esta notificación después de haberla emitido, actualice o cree un NotificationCompat.Builderobjeto, cree un objeto a Notificationpartir de él y emita Notificationel mismo ID que utilizó anteriormente. Si la notificación anterior aún está visible, el sistema la actualiza a partir del contenido del Notificationobjeto. Si se ha descartado la notificación anterior, se crea una nueva notificación.

Luca Manzo
fuente
35
¡ESTA ES LA RESPUESTA CORRECTA! La respuesta anterior es muy incorrecta y engañosa. No necesita reiniciar su servicio solo para actualizar una notificación tonta.
Radu
77
@Radu Si bien estoy de acuerdo en que esta es la respuesta óptima (evita la ruta de código ligeramente más larga tomada por la respuesta de Commonsware), está equivocado acerca de lo que hace la respuesta de Commonsware: iniciar / detener Foreoreund no inicia / detiene el servicio, solo afectan su primer plano .
Stevie
@ Stevie Gracias por ese Stevie probablemente tengas razón. ¡Aún así tampoco me metería con eso!
Radu
Llamar al notify()o startForeground()ambos conducen a la llamada onStartCommand().
M. Reza Nasirloo
10
El problema con el uso NotificationManagerpara actualizar una notificación que se muestra startForegroundes que las llamadas stopForegroundya no eliminarán la notificación. Actualizándolo con otra llamada para startForegroundevitar ese problema.
Tad
21

Mejorando la respuesta de Luca Manzo en Android 8.0+ al actualizar la notificación, emitirá un sonido y se mostrará como Heads-up.
para evitar que necesites agregarsetOnlyAlertOnce(true)

entonces el código es:

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);
}
humillado
fuente
Salvaste mi día. Gracias
Rubén Viguera
1
Falta la palabra clave, debe habernew NotificationChannel
7hny
5

Aquí está el código para hacerlo en su servicio . Cree una nueva notificación, pero solicite al administrador de notificaciones que notifique la misma identificación de notificación que utilizó en startForeground.

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

notificationManager.notify(ONGOING_NOTIFICATION, notify);

Para ver códigos de muestra completos, puede consultar aquí:

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

Daniel Kao
fuente
No estoy seguro de que esto mantendrá el estado de primer plano de startService.
Martin Marconcini
@Daniel Kao Su solución no inicia un servicio en primer plano
IgorGanapolsky
44
Corríjame si me equivoco, pero ¿podrían las personas que votan en contra de esta respuesta, por favor, sean más descriptivas de lo que está mal? La pregunta no pregunta cómo iniciar un servicio en primer plano, sino cómo actualizar una notificación de un servicio en primer plano. Esta es efectivamente la misma respuesta que Luca, que la gente considera que funciona y mantiene el estado de primer plano.
TheIT
@TheIT No funciona. El estado de la notificación se convierte not foregrounden foreground createdmensaje.
Vyacheslav
1
Esto daría como resultado una notificación duplicada, porque ya se llamó a startForeground () .
IgorGanapolsky
2

Parece que ninguna de las respuestas existentes muestran cómo manejar el caso completo: iniciar Groundground si es la primera llamada pero actualizar la notificación para las llamadas posteriores.

Puede usar el siguiente patrón para detectar el caso correcto:

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));
    }
}

Además, debe crear la notificación y el canal como en otras respuestas:

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
fuente