Recibe mensajes en una app para Android

Las notificaciones de Firebase se comportan de diferente manera, según el estado de primer plano o segundo plano de la app que las recibe. Si quieres que las apps en primer plano reciban mensajes de notificación o mensajes de datos, deberás escribir código para administrar la devolución de llamada onMessageReceived. Para ver una explicación de la diferencia entre los mensajes de notificación y los de datos, consulta los tipos de mensaje.

Administración de mensajes

Para recibir mensajes, usa un servicio que extienda FirebaseMessagingService. El servicio debe anular las devoluciones de llamada onMessageReceived y onDeletedMessages.

onMessageReceived se proporciona para la mayoría de los tipos de mensajes, con las siguientes excepciones:

  • Mensajes de notificación enviados cuando la app está en segundo plano. En este caso, la notificación se entrega a la bandeja del sistema del dispositivo. Cuando un usuario presiona una notificación, abre el selector de aplicaciones de forma predeterminada.

  • Mensajes con una notificación y carga útil de datos cuando se reciben en segundo plano. En este caso, la notificación se entrega a la bandeja del sistema del dispositivo, y la carga útil de datos se entrega en los adicionales del intent de tu actividad iniciadora.

Resumen:

Estado de la app Notificación Datos Ambos
Primer plano onMessageReceived onMessageReceived onMessageReceived
Segundo plano Bandeja del sistema onMessageReceived Notificación: bandeja del sistema
Datos: en adicionales del intent
Para obtener más información acerca de los tipos de mensajes, consulta Mensajes de notificación y de datos.

La devolución de llamada onMessageReceived recibe tiempos de espera que te permiten publicar una notificación de forma sencilla, pero los temporizadores no están diseñados para permitir que la app acceda a la red o realice trabajo adicional. Por lo tanto, si tu app hace algo más complicado, debes realizar trabajo adicional para asegurarte de que pueda completar su trabajo.

Si crees que tu app podría tardar cerca de 10 segundos en controlar un mensaje, debes programar un trabajo de WorkManager o seguir la orientación sobre WakeLock que se indica a continuación. En algunos casos, el período para manejar un mensaje puede ser inferior a 10 segundos, según los retrasos en los que se incurrió antes de llamar a onMessageReceived, incluidos los retrasos del SO, el tiempo de inicio de la app, el subproceso principal que bloquearon otras operaciones o las llamadas a onMessageReceived previas que demoran demasiado. Después de que venza ese temporizador, es posible que tu app esté sujeta a la eliminación de procesos o a los límites de ejecución en segundo plano. Ten en cuenta que las latencias de las transacciones de red y el inicio de la app pueden ser significativas, por lo que, en caso de duda, planifica que el procesamiento de mensajes se ejecute durante mucho tiempo si hay dependencias asíncronas, como acceso a la red o requisitos de carga de datos intensivos.

Edita el manifiesto de la app

Para usar FirebaseMessagingService, deberás agregar lo siguiente en el manifiesto de la app:

<service
    android:name=".java.MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

Además, te recomendamos establecer valores predeterminados para personalizar el aspecto de las notificaciones. Puedes especificar un ícono y un color predeterminados y personalizados que se aplican cada vez que no se establecen valores equivalentes en la carga útil de notificación.

Agrega estas líneas en la etiqueta application para establecer el ícono predeterminado y el color personalizados:

<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
     See README(https://goo.gl/l4GJaQ) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_stat_ic_notification" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
     notification message. See README(https://goo.gl/6BKBk7) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/colorAccent" />

Android muestra el ícono predeterminado personalizado para lo siguiente:

  • Todos los mensajes de notificación enviados desde el Compositor de Notifications
  • Todos los mensajes de notificación que no establecen el ícono de manera explícita en la carga útil de la notificación

Android usa el color predeterminado personalizado para lo siguiente:

  • Todos los mensajes de notificación enviados desde el Compositor de Notifications
  • Todos los mensajes de notificación que no definen el color de manera explícita en la carga útil de la notificación

Si no hay un ícono predeterminado personalizado ni se establece un ícono en la carga útil de la notificación, Android muestra el ícono de la aplicación en blanco.

Anula onMessageReceived

Puedes anular el método FirebaseMessagingService.onMessageReceived para realizar acciones según el objeto RemoteMessage recibido y obtener los datos del mensaje de la siguiente manera:

Kotlin

override fun onMessageReceived(remoteMessage: RemoteMessage) {
    // TODO(developer): Handle FCM messages here.
    // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
    Log.d(TAG, "From: ${remoteMessage.from}")

    // Check if message contains a data payload.
    if (remoteMessage.data.isNotEmpty()) {
        Log.d(TAG, "Message data payload: ${remoteMessage.data}")

        // Check if data needs to be processed by long running job
        if (needsToBeScheduled()) {
            // For long-running tasks (10 seconds or more) use WorkManager.
            scheduleJob()
        } else {
            // Handle message within 10 seconds
            handleNow()
        }
    }

    // Check if message contains a notification payload.
    remoteMessage.notification?.let {
        Log.d(TAG, "Message Notification Body: ${it.body}")
    }

    // Also if you intend on generating your own notifications as a result of a received FCM
    // message, here is where that should be initiated. See sendNotification method below.
}

Java

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    // TODO(developer): Handle FCM messages here.
    // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
    Log.d(TAG, "From: " + remoteMessage.getFrom());

    // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "Message data payload: " + remoteMessage.getData());

        if (/* Check if data needs to be processed by long running job */ true) {
            // For long-running tasks (10 seconds or more) use WorkManager.
            scheduleJob();
        } else {
            // Handle message within 10 seconds
            handleNow();
        }

    }

    // Check if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {
        Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
    }

    // Also if you intend on generating your own notifications as a result of a received FCM
    // message, here is where that should be initiated. See sendNotification method below.
}

Mantén activo el dispositivo mientras se controlan los mensajes de FCM

Si tu app necesita mantener el dispositivo activo mientras procesa un mensaje de FCM, deberá mantener un WakeLock durante este tiempo o crear un trabajo de WorkManager. Los WakeLocks funcionan bien para actividades de procesamiento cortas que podrían exceder los tiempos de espera predeterminados de onMessageReceived. Para flujos de trabajo extendidos, como el envío de varias RPC seriales a tus servidores, es más apropiado usar un trabajo de WorkManager que un WakeLock. En esta sección, nos enfocaremos en cómo usar WakeLocks. Un WakeLock evita que el dispositivo se suspenda mientras se ejecuta tu app, lo que puede generar un mayor uso de la batería, por lo que el uso de WakeLocks debe reservarse para los casos en los que tu app no debe pausarse mientras controla el mensaje, como los siguientes:

  • Notificaciones urgentes para el usuario
  • Interacciones con algo fuera del dispositivo que no se deben interrumpir (como transferencias de red o comunicaciones con otro dispositivo, como un reloj vinculado)

Primero, deberás asegurarte de que tu app solicite el permiso WakeLock (el SDK de FCM lo incluye de forma predeterminada, por lo que, normalmente, no es necesario agregar nada).

<uses-permission android:name="android.permission.WAKE_LOCK" />

Luego, tu app deberá adquirir un WakeLock al inicio de la devolución de llamada de FirebaseMessagingService.onMessageReceived() y liberarlo al final de la devolución de llamada.

FirebaseMessagingService personalizado de la app:

@Override
public void onMessageReceived(final RemoteMessage message) {
  // If this is a message that is time sensitive or shouldn't be interrupted
  WakeLock wakeLock = getSystemService(PowerManager.class).newWakeLock(PARTIAL_WAKE_LOCK, "myApp:messageReceived");
  try {
    wakeLock.acquire(TIMEOUT_MS);
    // handle message
    ...
  finally {
    wakeLock.release();
  }
}

Anula onDeletedMessages

En algunas situaciones, es posible que FCM no envíe un mensaje. Esto ocurre cuando hay muchos mensajes (más de 100) pendientes para tu app en un dispositivo específico al momento de conectarse o si el dispositivo no se conecta a FCM durante más de un mes. En estos casos, es posible que recibas una devolución de llamada a FirebaseMessagingService.onDeletedMessages(). Cuando la instancia de app recibe esta devolución de llamada, debería ejecutar una sincronización completa con tu servidor de apps. Si no enviaste un mensaje a la app en ese dispositivo en las últimas 4 semanas, FCM no llamará a onDeletedMessages().

Maneja mensajes de notificación en una app en segundo plano

Cuando tu app está en segundo plano, Android dirige los mensajes de notificación a la bandeja del sistema. Cuando un usuario presiona una notificación, se abre el selector de aplicaciones de forma predeterminada.

Esto incluye los mensajes que contienen una notificación y carga útil de datos (y todos los mensajes que se envíen desde la consola de Notifications). En estos casos, la notificación se entrega a la bandeja del sistema del dispositivo, y la carga de datos útil se entrega en los adicionales del intent de tu actividad iniciadora.

Para obtener información sobre la entrega de mensajes en tu app, consulta el panel de informes de FCM, en el que se registra la cantidad de mensajes que se enviaron y abrieron en dispositivos Apple y Android, junto con datos de "impresiones" (las notificaciones que ven los usuarios) de las apps para Android.

Recibe mensajes de FCM en modo de inicio directo

Los desarrolladores que quieran enviar mensajes de FCM a las apps incluso antes de que se desbloquee el dispositivo pueden habilitar una app para Android que reciba mensajes cuando el dispositivo esté en modo de inicio directo. Por ejemplo, recomendamos que los usuarios de tu app reciban notificaciones de alarma incluso si los dispositivos están bloqueados.

Cuando crees este caso de uso, deberás cumplir con las prácticas recomendadas y restricciones generales para el modo de inicio directo. Es muy importante tener en cuenta la visibilidad de los mensajes habilitados para inicio directo: cualquier usuario con acceso al dispositivo puede verlos sin ingresar sus credenciales.

Requisitos previos

  • El dispositivo debe tener configurado el modo de inicio directo.
  • El dispositivo debe tener instalada una versión reciente de los Servicios de Google Play (19.0.54 o posterior).
  • La app debe usar el SDK de FCM (com.google.firebase:firebase-messaging) para recibir mensajes de ese servicio.

Habilita el controlador de mensajes en modo de inicio directo en tu app

  1. En el archivo Gradle de nivel de la app, agrega una dependencia en la biblioteca de compatibilidad de inicio directo de FCM:

    implementation 'com.google.firebase:firebase-messaging-directboot:20.2.0'
    
  2. Agrega el atributo android:directBootAware="true" en el manifiesto de la app para que el inicio directo FirebaseMessagingService sea compatible:

    <service
        android:name=".java.MyFirebaseMessagingService"
        android:exported="false"
        android:directBootAware="true">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>
    

Es importante asegurarse de que FirebaseMessagingService se pueda ejecutar en modo de inicio directo. Para ello verifica los siguientes requisitos:

  • El servicio no debe acceder al almacenamiento protegido por credenciales mientras se ejecuta en modo de inicio directo.
  • El servicio no debe intentar usar componentes, como Activities, BroadcastReceivers o cualquier otro Services, que no estén marcados como compatibles con inicio directo mientras se ejecutan en este modo.
  • Las bibliotecas que use el servicio tampoco deben acceder al almacenamiento protegido por credenciales ni llamar a componentes que no sean compatibles con el inicio directo mientras se ejecutan en ese modo. Es decir, las bibliotecas que use la app y que se llamen desde el servicio deben ser compatibles con el inicio directo, o bien la app deberá verificar si el servicio se está ejecutando en modo de inicio directo y no llamarlas mientras esté en ese modo. Por ejemplo, los SDK de Firebase funcionan con inicio directo (se pueden incluir en una app sin producir fallas en ese modo); sin embargo, muchas API de Firebase no admiten las llamadas en modo de inicio directo.
  • Si la app usa un código Application personalizado, Application también debe ser compatible con el inicio directo (es decir, no tener acceso al almacenamiento protegido por credenciales).

Si quieres obtener ayuda para enviar mensajes a dispositivos en modo de inicio directo, consulta Envía mensajes con el inicio directo habilitado.