Объединение уведомлений на мобильном устройстве

Начиная с уровня API 26 для активных служб требуются постоянные уведомления. Это требование призвано предотвратить скрытие сервисов, которые могут чрезмерно нагружать системные ресурсы, в частности батарею. Это требование создает потенциальную проблему: если приложение с несколькими активными службами не управляет уведомлениями так, чтобы они были общими для всех служб, то может появиться несколько постоянных уведомлений, которые нельзя закрыть. Это приведет к нежелательному загромождению списка активных уведомлений.

Проблема усугубляется, если вы используете SDK, например Navigation SDK, которые запускают активные службы независимо от приложения и имеют собственные независимые постоянные уведомления, что затрудняет их объединение. Чтобы решить эти проблемы, в Navigation SDK версии 1.11 был добавлен простой API, который помогает управлять постоянными уведомлениями в приложении, в том числе в SDK.

Объединять постоянные уведомления

Компоненты

Менеджер активных служб предоставляет оболочку для класса активных служб Android и класса постоянных уведомлений. Основная функция этой оболочки – обеспечить повторное использование идентификатора уведомления, чтобы уведомление было общим для всех активных служб, использующих менеджер.


Navigation SDK содержит статические методы для инициализации и получения синглтона ForegroundServiceManager. Этот синглтон можно инициализировать только один раз за время существования Navigation SDK. Поэтому, если вы используете один из вызовов инициализации (initForegroundServiceManagerMessageAndIntent() или initForegroundServiceManagerProvider()), вам следует окружить его блоком try-catch на случай повторного входа в этот путь. Если вы вызовете любой из этих методов более одного раза, Navigation SDK выдаст ошибку времени выполнения. Чтобы этого не произошло, сначала удалите все ссылки на ForegroundServiceManager и вызовите clearForegroundServiceManager() перед каждым последующим вызовом.

Четыре параметра initForegroundServiceManagerMessageAndIntent() – это application, notificationId, defaultMessage и resumeIntent. Если последние три параметра имеют значение null, то уведомление будет стандартным уведомлением Navigation SDK. Однако за этим уведомлением все равно можно скрыть другие активные службы. Параметр notificationId указывает идентификатор, который должен использоваться для уведомления. Если значение null, используется произвольное значение. Вы можете задать его явным образом, чтобы избежать конфликтов с другими уведомлениями, например от другого SDK. defaultMessage – это строка, которая показывается, когда система не выполняет навигацию. resumeIntent – это намерение, которое активируется при нажатии на уведомление. Если resumeIntent имеет значение null, то клики по уведомлению игнорируются.

Три параметра initForegroundServiceManagerProvider() – это application, notificationId и notificationProvider. Если последние два параметра имеют значение null, то уведомление является стандартным уведомлением Navigation SDK. Параметр notificationId указывает идентификатор, который должен использоваться для уведомления. Если значение null, используется произвольное значение. Вы можете задать его явным образом, чтобы избежать конфликтов с другими уведомлениями, например от другого SDK. Если задано свойство notificationProvider, то поставщик всегда отвечает за создание уведомления, которое будет показано.

Метод getForegroundServiceManager() Navigation SDK возвращает синглтон менеджера активных служб. Если вы ещё не создали его, это эквивалентно вызову initForegroundServiceManagerMessageAndIntent() с нулевыми параметрами для notificationId, defaultMessage и resumeIntent.

В классе ForegroundServiceManager есть три простых метода. Первые два метода используются для перемещения службы на передний план и обратно и обычно вызываются из созданной службы. Это гарантирует, что службы будут связаны с общим постоянным уведомлением. Последний метод, updateNotification(), сообщает менеджеру, что уведомление изменилось и его нужно перерисовать.

Если вам нужен полный контроль над общим постоянным уведомлением, API предоставляет интерфейс NotificationContentProvider для определения поставщика уведомлений, который содержит один метод для получения уведомления с текущим контентом. Также предоставляется базовый класс, который можно использовать для определения поставщика. Одна из основных целей базового класса – предоставить способ вызова updateNotification() без необходимости доступа к ForegroundServiceManager. Если вы используете экземпляр поставщика уведомлений для получения новых сообщений, вы можете вызвать этот внутренний метод напрямую, чтобы отобразить сообщение в уведомлении.

Сценарии использования

В этом разделе подробно описаны сценарии использования общих постоянных уведомлений.

Скрывать постоянные уведомления активных служб других приложений
Самый простой вариант – сохранить текущее поведение и использовать постоянное уведомление только для отображения информации из Navigation SDK. Другие сервисы могут скрываться за этим уведомлением, используя методы startForeground() и stopForeground() менеджера активных служб.
Скрывать постоянные уведомления активных служб других приложений, но задать текст по умолчанию, который будет показываться, когда навигация не используется.
Второй по простоте сценарий – сохранить текущее поведение и использовать постоянное уведомление только для показа информации из Navigation SDK, за исключением случаев, когда навигация не выполняется. Когда система не выполняет навигацию, вместо стандартной строки Navigation SDK, в которой упоминаются Google Карты, показывается строка, переданная в initForegroundServiceManagerMessageAndIntent(). Этот вызов также можно использовать, чтобы задать намерение возобновления, которое срабатывает при нажатии на уведомление.
Полный контроль над отображением постоянного уведомления.
В последнем случае необходимо определить и создать поставщика уведомлений, а затем передать его в ForegroundServiceManager с помощью initForegroundServiceManagerProvider(). Этот вариант позволяет полностью контролировать, что отображается в уведомлении, но при этом отключает информацию из Navigation SDK, в том числе полезные пошаговые подсказки. Google не предоставляет простого способа получить эту информацию и вставить ее в уведомление.

Пример поставщика уведомлений

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

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

После создания NotificationContentProviderImpl подключите к нему Navigation SDK с помощью следующего кода:

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

Ограничения и планы на будущее

  • Вызовите initForegroundServiceManagerMessageAndIntent() или initForegroundServiceManagerProvider() на раннем этапе, чтобы определить ожидаемый сценарий использования. Этот метод необходимо вызвать до создания нового объекта Navigator.
  • Обязательно перехватывайте исключения из вызовов initForegroundServiceManagerMessageAndIntent() или initForegroundServiceManagerProvider() на случай, если путь кода будет введен более одного раза. В Navigation SDK версии 2.0 многократный вызов этого метода вызывает проверяемое исключение, а не исключение времени выполнения.
  • Google все ещё работает над тем, чтобы стиль уведомления соответствовал стилю заголовка на протяжении всего времени показа.
  • При определении поставщика уведомлений вы можете управлять поведением всплывающих уведомлений с помощью приоритета.
  • Google не предоставляет простого способа получения пошаговой информации, которую поставщик уведомлений может вставить в уведомление.