整合移动通知

从 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() 的四个参数分别是 application、notificationId、defaultMessage 和 resumeIntent。如果最后三个参数为 null,则通知为标准 Navigation SDK 通知。应用中的其他前台服务仍有可能隐藏在此通知后面。notificationId 参数指定应为通知使用的通知 ID。如果该值为 null,则使用任意值。您可以明确设置该 ID,以避免与其他通知(例如来自其他 SDK 的通知)发生冲突。defaultMessage 是系统未导航时显示的字符串。resumeIntent 是在用户点击通知时触发的 intent。如果 resumeIntent 为 null,则系统会忽略对通知的点击。

initForegroundServiceManagerProvider() 的三个参数分别为 application、notificationId 和 notificationProvider。如果最后两个参数为 null,则通知为标准 Navigation SDK 通知。notificationId 参数指定应为通知使用的通知 ID。如果该值为 null,则使用任意值。您可以明确设置此 ID,以避免与其他通知(例如来自其他 SDK 的通知)发生冲突。如果设置了 notificationProvider,则提供程序始终负责生成要呈现的通知。

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

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

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

使用场景

本部分详细介绍了使用共享常驻通知的使用场景。

隐藏其他应用前台服务的常驻通知
最简单的情况是保留当前行为,仅使用常驻通知来呈现 Navigation SDK 信息。其他服务可以使用前台服务管理器 startForeground() 和 stopForeground() 方法隐藏在此通知后面。
隐藏其他应用前台服务的常驻通知,但设置未导航时显示的默认文本
第二种最简单的情况是保留当前行为,并且仅使用常驻通知来呈现 Navigation SDK 信息,除非系统未在导航。当系统未进行导航时,系统会显示提供给 initForegroundServiceManagerMessageAndIntent() 的字符串,而不是提及“Google 地图”的默认 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(),以便明确定义预期的使用场景。您必须先调用此方法,然后才能创建新的 Navigator。
  • 请务必捕获对 initForegroundServiceManagerMessageAndIntent() 或 initForegroundServiceManagerProvider() 的调用中的异常,以防多次进入代码路径。在 Navigation SDK v2.0 中,多次调用此方法会抛出受检异常,而不是运行时异常。
  • Google 可能仍需要做一些工作,才能在通知的整个生命周期内实现与标题样式一致的样式。
  • 定义通知提供程序时,您可以使用优先级控制浮动通知行为。
  • Google 不提供简单的方法来检索通知提供程序可能插入到通知中的精细导航信息。