整合移动通知

从 Android API 级别 26 开始,前台服务需要持久性通知。此要求旨在防止您隐藏可能会对系统资源(尤其是电池)要求过多的服务。此要求会带来潜在问题:如果具有多个前台服务的应用没有仔细管理通知,使其在所有服务之间共享,则可能存在多个无法关闭的持续通知,从而导致活动通知列表中出现不受欢迎的混乱。

如果您使用的 SDK(例如 Navigation SDK)独立于应用运行前台服务,并且具有独立持久性通知,那么这些 SDK 难以整合,因此这个问题变得更加困难。为了解决这些问题,Navigation SDK v1.11 引入了一个简单的 API 来帮助管理整个应用(包括 SDK 内)的持久性通知。

整合常驻通知

组件

前台服务管理器提供一个封装了 Android 前台服务类和持久性通知类的封装容器。此封装容器的主要功能是强制重复使用通知 ID,以便使用管理器在所有前台服务之间共享通知。


Navigation SDK 包含用于初始化和获取 ForegroundServiceManager 单例的静态方法。在 Navigation SDK 的生命周期内,此单例只能初始化一次。因此,如果您使用其中一个初始化调用(initForegroundServiceManagerMessageAndIntent()initForegroundServiceManagerProvider()),则应将其用 try-catch 块括起来,以防重新进入该路径。如果您多次调用任一方法,除非您先清除对 ForegroundServiceManager 的所有引用,并在每次后续调用之前调用 clearForegroundServiceManager(),否则 Navigation SDK 会抛出运行时异常。

initForegroundServiceManagerMessageAndIntent() 的四个参数为 applicationnotificationIddefaultMessageresumeIntent。如果最后三个参数为 null,则通知是标准 Navigation SDK 通知。您仍然可以在此通知后面隐藏该应用中的其他前台服务。notificationId 参数指定应用于通知的通知 ID。如果为 null,则使用任意值。您可以明确设置它来解决与其他通知(例如来自其他 SDK 的通知)的冲突问题。defaultMessage 是一个在系统未导航时显示的字符串。resumeIntent 是点击通知时触发的 intent。如果 resumeIntent 为 null,系统会忽略对通知的点击。

initForegroundServiceManagerProvider() 的三个参数是 applicationnotificationIdnotificationProvider。如果最后两个参数为 null,则通知是标准 Navigation SDK 通知。notificationId 参数指定应用于通知的通知 ID。如果为 null,则使用任意值。您可以明确进行设置以解决与其他通知(例如来自其他 SDK 的通知)的冲突。如果设置了 notificationProvider,则提供程序始终负责生成要呈现的通知。

Navigation SDK getForegroundServiceManager() 方法会返回前台服务管理器单例。如果尚未生成,则相当于使用 null 参数为 notificationIddefaultMessageresumeIntent 调用 initForegroundServiceManagerMessageAndIntent()

ForegroundServiceManager 有三个简单的方法。前两个用于将服务移入和移出前台,通常从已创建的服务内调用。使用这些方法可确保服务与共享持久性通知相关联。最后一种方法是 updateNotification(),它会标记管理器,通知已发生变化,应重新呈现。

如果您需要完全控制共享永久性通知,可以使用该 API 提供的 NotificationContentProvider 接口来定义通知提供程序,该接口包含用于获取包含当前内容的通知的单一方法。此外,它还提供了一个基类,您可以选择使用它来帮助定义提供程序。基类的主要用途之一是提供一种无需访问 ForegroundServiceManager 即可调用 updateNotification() 的方法。如果您使用通知提供程序的实例接收新的通知消息,可以直接调用此内部方法以在通知中呈现消息。

使用场景

本部分详细介绍了使用共享永久性通知的使用场景。

隐藏其他应用前台服务的持久性通知
最简单的场景是保留当前行为,仅使用常驻通知来呈现 Navigation SDK 信息。其他服务可以使用前台服务管理器 startForeground()stopForeground() 方法隐藏此通知。
隐藏其他应用前台服务的持久性通知,但设置不在导航时显示的默认文本
第二个最简单的场景是保留当前行为,并且仅使用永久通知来呈现 Navigation SDK 信息(系统未进行导航时除外)。系统未进行导航时,系统会显示提供给 initForegroundServiceManagerMessageAndIntent() 的字符串,而不是提及“Google Maps”的默认 Navigation SDK 字符串。您还可以使用此调用来设置在点击通知时触发的恢复 intent。
完全控制常驻通知的呈现
最后一种场景需要定义和创建通知提供程序,并使用 initForegroundServiceManagerProvider() 将其传递给 ForegroundServiceManager。此选项可让您完全控制通知中呈现的内容,但也会断开 Navigation SDK 通知信息与通知的连接,从而移除通知中显示的实用精细的精细导航提示。Google 没有提供一种简单的方法来检索此信息并将其插入通知。

通知提供程序示例

以下代码示例演示了如何使用简单的通知 content provider 创建和返回通知。

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(),以便明确定义预期的使用场景。您必须在创建新的导航器之前调用此方法。
  • 请务必捕获 initForegroundServiceManagerMessageAndIntent()initForegroundServiceManagerProvider() 调用中的异常,以防多次输入代码路径。在 Navigation SDK v2.0 中,多次调用此方法会抛出已检查的异常,而不是运行时异常。
  • Google 可能仍有办法在通知的整个生命周期内与标头样式保持一致的样式。
  • 定义通知提供程序时,您可以使用优先级来控制浮动行为。
  • Google 没有提供一种简单的方法来检索通知提供程序可能会在通知中插入的精细信息。