Consolidar notificações em dispositivos móveis

A partir do nível 26 da API do Android, as notificações persistentes são obrigatórias para serviços em primeiro plano. Esse requisito serve para impedir que você oculte serviços que possam exigir demais dos recursos do sistema, incluindo a bateria em particular. Esse requisito cria um problema em potencial: se um app com vários serviços em primeiro plano não gerenciar a notificação com cuidado para que ela seja compartilhada entre todos os serviços, pode haver várias notificações persistentes que não podem ser dispensadas, levando a uma confusão indesejada na lista ativa de notificações.

Esse problema se torna mais desafiador quando você usa SDKs como o SDK de navegação, que executam serviços em primeiro plano independentes do app e têm as próprias notificações persistentes independentes, dificultando a consolidação. Para resolver esses problemas, o SDK de navegação v1.11 introduziu uma API simples para ajudar a gerenciar notificações persistentes em todo o app, inclusive no SDK.

Consolidar notificações persistentes

Componentes

O gerenciador de serviços em primeiro plano fornece um wrapper para a classe de serviço em primeiro plano do Android e a classe de notificação persistente. A função principal desse wrapper é aplicar a reutilização do ID de notificação para que a notificação seja compartilhada entre todos os serviços em primeiro plano que usam o gerenciador.


O SDK de navegação contém métodos estáticos para inicializar e receber o singleton ForegroundServiceManager. Esse singleton só pode ser inicializado uma vez durante a vida útil do SDK de navegação. Consequentemente, se você usar uma das chamadas de inicialização (initForegroundServiceManagerMessageAndIntent() ou initForegroundServiceManagerProvider()), envolva-a com um bloco try-catch caso esse caminho seja inserido novamente. O SDK do Navigation vai gerar uma exceção de tempo de execução se você chamar qualquer um dos métodos mais de uma vez, a menos que primeiro limpe todas as referências ao ForegroundServiceManager e chame clearForegroundServiceManager() antes de cada chamada subsequente.

Os quatro parâmetros de initForegroundServiceManagerMessageAndIntent() são application, notificationId, defaultMessage e resumeIntent. Se os três últimos parâmetros forem nulos, a notificação será a padrão do SDK de navegação. Ainda é possível ocultar outros serviços em primeiro plano no app por trás dessa notificação. O parâmetro notificationId especifica o ID que deve ser usado para a notificação. Se for nulo, um valor arbitrário será usado. É possível defini-lo explicitamente para contornar conflitos com outras notificações, como as de outro SDK. O defaultMessage é uma string mostrada quando o sistema não está navegando. O resumeIntent é uma intent que é disparada quando a notificação é clicada. Se o resumeIntent for nulo, os cliques na notificação serão ignorados.

Os três parâmetros de initForegroundServiceManagerProvider() são application, notificationId e notificationProvider. Se os dois últimos parâmetros forem nulos, a notificação será a padrão do SDK do Navigation. O parâmetro notificationId especifica o ID que deve ser usado para a notificação. Se for nulo, um valor arbitrário será usado. É possível defini-lo explicitamente para evitar conflitos com outras notificações, como as de outro SDK. Se o notificationProvider estiver definido, o provedor será sempre responsável por gerar a notificação a ser renderizada.

O método getForegroundServiceManager() do SDK do Navigation retorna o singleton do gerenciador de serviços em primeiro plano. Se você ainda não tiver gerado um, isso equivale a chamar initForegroundServiceManagerMessageAndIntent() com parâmetros nulos para notificationId, defaultMessage e resumeIntent.

O ForegroundServiceManager tem três métodos simples. Os dois primeiros são para mover um serviço para dentro e para fora do primeiro plano e geralmente são chamados de dentro do serviço que foi criado. O uso desses métodos garante que os serviços sejam associados à notificação persistente compartilhada. O método final, updateNotification(), sinaliza ao gerenciador que a notificação foi alterada e precisa ser renderizada novamente.

Se você precisar de controle total sobre a notificação persistente compartilhada, a API vai fornecer uma interface NotificationContentProvider para definir um provedor de notificações, que contém um único método para receber uma notificação com o conteúdo atual. Ele também fornece uma classe de base, que pode ser usada para ajudar a definir o provedor. Um dos principais objetivos da classe de base é fornecer uma maneira de chamar updateNotification() sem a necessidade de acessar o ForegroundServiceManager. Se você usar uma instância do provedor de notificações para receber novas mensagens de notificação, poderá chamar esse método interno diretamente para renderizar a mensagem na notificação.

cenários de uso

Nesta seção, detalhamos os cenários de uso de notificações persistentes compartilhadas.

Ocultar notificações persistentes de outros serviços em primeiro plano de apps
O cenário mais fácil é preservar o comportamento atual e usar apenas a notificação persistente para renderizar as informações do SDK do Navigation. Outros serviços podem se ocultar por trás dessa notificação usando os métodos startForeground() e stopForeground() do gerenciador de serviços em primeiro plano.
Ocultar notificações persistentes de outros serviços em primeiro plano do app, mas definir o texto padrão mostrado quando não estiver navegando
O segundo cenário mais fácil é preservar o comportamento atual e usar apenas a notificação persistente para renderizar as informações do SDK do Navigation, exceto quando o sistema não estiver navegando. Quando o sistema não está navegando, a string fornecida para initForegroundServiceManagerMessageAndIntent() é exibida em vez da string padrão do SDK do Navigation que menciona "Google Maps". Também é possível usar essa chamada para definir a intent de retomada que é acionada quando a notificação é clicada.
Assuma o controle total da renderização da notificação persistente
O cenário final exige a definição e a criação de um provedor de notificações e a transmissão dele para o ForegroundServiceManager usando initForegroundServiceManagerProvider(). Essa opção oferece controle total sobre o que é renderizado na notificação, mas também desconecta as informações de notificação do SDK de navegação da notificação, removendo as instruções de navegação guiada mostradas na notificação. O Google não oferece uma maneira simples de recuperar essas informações e inseri-las na notificação.

Exemplo de provedor de notificações

O exemplo de código a seguir demonstra como criar e retornar notificações usando um provedor de conteúdo de notificação simples.

public class NotificationContentProviderImpl
   extends NotificationContentProviderBase
   implements NotificationContentProvider {
 private String channelId;
 private Context context;
 private String message;

 /** Constructor */
 public NotificationContentProviderImpl(Application application) {
   super(application);
   message = "-- uninitialized --";
   channelId = null;
   this.context = application;
 }

 /**
  * Sets message to display in the notification. Calls updateNotification
  * to display the message immediately.
  *
  * @param msg The message to display in the notification.
  */
 public void setMessage(String msg) {
   message = msg;
   updateNotification();
 }

 /**
  * Returns the notification as it should be rendered.
  */
 @Override
 public Notification getNotification() {
   Notification notification;

   if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
     Spanned styledText = Html.fromHtml(message, FROM_HTML_MODE_LEGACY);
     String channelId = getChannelId(context);
     notification =
         new Notification.Builder(context, channelId)
             .setContentTitle("Notifications Demo")
             .setStyle(new Notification.BigTextStyle()
                 .bigText(styledText))
             .setSmallIcon(R.drawable.ic_navigation_white_24dp)
             .setTicker("ticker text")
             .build();
   } else {
     notification = new Notification.Builder(context)
         .setContentTitle("Notification Demo")
         .setContentText("testing non-O text")
         .build();
   }

   return notification;
 }

 // Helper to set up a channel ID.
 private String getChannelId(Context context) {
   if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
     if (channelId == null) {
       NotificationManager notificationManager =
           (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
       NotificationChannel channel = new NotificationChannel(
           "default", "navigation", NotificationManager.IMPORTANCE_DEFAULT);
       channel.setDescription("For navigation persistent notification.");
       notificationManager.createNotificationChannel(channel);
       channelId = channel.getId();
     }
     return channelId;
   } else {
     return "";
   }
 }
}

Depois de criar NotificationContentProviderImpl, conecte o SDK de navegação a ele usando o seguinte código:

ForegroundServiceManager f = NavigationApi.getForegroundServiceManager(getApplication());
mNotification = new NotificationContentProviderImpl(getApplication());
NavigationApi.clearForegroundServiceManager();
NavigationApi.initForegroundServiceManagerProvider(getApplication(), null, mNotification);

Advertências e planos futuros

  • Chame initForegroundServiceManagerMessageAndIntent() ou initForegroundServiceManagerProvider() com antecedência para que o cenário de uso esperado seja bem definido. Chame esse método antes de criar um novo Navigator.
  • Capture exceções de chamadas para initForegroundServiceManagerMessageAndIntent() ou initForegroundServiceManagerProvider() caso o caminho do código seja inserido mais de uma vez. No SDK do Navigation v2.0, chamar esse método várias vezes gera uma exceção verificada em vez de uma exceção de ambiente de execução.
  • O Google ainda pode ter trabalho a fazer para conseguir um estilo consistente durante o ciclo de vida da notificação que corresponda ao estilo do cabeçalho.
  • Ao definir um provedor de notificações, é possível controlar o comportamento de alerta com a prioridade.
  • O Google não oferece uma maneira simples de recuperar informações de navegação guiada que um provedor de notificações pode inserir na notificação.