整合移动通知

从 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() 方法会返回前台服务管理器单例。如果尚未生成,则相当于使用 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 没有提供一种简单的方法来检索通知提供商可能插入到通知中的精细导航信息。