整合移动通知

从 Android API 级别 26 开始,前台服务必须具有持久性通知。此要求旨在防止您隐藏可能对系统资源(尤其是电池)造成过多需求的服务。这项要求会导致一个潜在问题:如果具有多个前台服务的应用未妥善管理通知,以便在所有服务中共享该通知,则可能会有多个无法关闭的永久性通知,导致活跃通知列表中出现不必要的杂乱。

如果您使用 Navigation SDK 等 SDK,这些 SDK 会独立于具有自己独立常驻通知的应用运行前台服务,这会使这些通知难以合并,从而加大了解决此问题的难度。为了解决这些问题,Navigation SDK v1.11 引入了一个简单的 API,以帮助管理应用(包括 SDK 内)中的永久性通知。

合并持久性通知

组件

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


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

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() 方法会返回前台服务管理器单例。如果您尚未生成此类实例,则相当于使用 notificationIddefaultMessageresumeIntent 的 null 参数调用 initForegroundServiceManagerMessageAndIntent()

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

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

使用场景

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

隐藏其他应用前台服务的持久性通知
最简单的情况是保留当前行为,仅使用持久性通知来呈现 Navigation SDK 信息。其他服务可以使用前台服务管理器 startForeground()stopForeground() 方法隐藏在该通知后面。
隐藏其他应用前台服务的持久性通知,但设置在未导航时显示的默认文本
第二种最简单的情况是保留当前行为,仅在系统未导航时使用持久性通知来呈现 Navigation SDK 信息。当系统未导航时,系统会显示提供给 initForegroundServiceManagerMessageAndIntent() 的字符串,而不是提及“Google 地图”的默认 Navigation SDK 字符串。您还可以使用此调用来设置在用户点击通知时触发的 resume intent。
完全控制永久性通知的呈现
最后一种场景需要定义和创建通知提供程序,并使用 initForegroundServiceManagerProvider() 将其传递给 ForegroundServiceManager。通过此选项,您可以完全控制在通知中呈现的内容,但这也会将 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 v2.0 中,多次调用此方法会抛出检查异常,而不是运行时异常。
  • Google 可能仍需要努力,才能在通知的整个生命周期内实现与标题样式一致的样式。
  • 定义通知提供程序时,您可以使用优先级来控制动作条行为。
  • Google 不提供用于检索通知提供程序可能会插入通知中的精细导航信息的简单方法。