整合行動裝置通知

從 Android API 級別 26 開始,前景服務必須使用持續性通知。這項規定是為了避免您隱藏可能過度耗用系統資源 (尤其是電池) 的服務。這項規定可能會造成問題:如果應用程式有多個前景服務,但未妥善管理通知,導致所有服務共用同一個通知,則可能會出現多個無法關閉的持續性通知,造成通知的有效清單過於雜亂。

如果您使用 Navigation SDK 等 SDK,問題會變得更加棘手,因為這些 SDK 會獨立於應用程式執行前景服務,並顯示自己的獨立常駐通知,因此難以整合。為解決這些問題,Navigation SDK 1.11 版推出簡單的 API,協助管理應用程式 (包括 SDK 內) 的持續性通知。

整合持續性通知

元件

前景服務管理工具會為 Android 前景服務類別和持續性通知類別提供包裝函式。這個封裝函式的主要功能是強制重複使用通知 ID,以便透過管理工具在所有前景服務之間共用通知。


Navigation SDK 包含用於初始化及取得 ForegroundServiceManager 單例項的靜態方法。這個單例項只能在 Navigation SDK 的生命週期內初始化一次。因此,如果您使用其中一個初始化呼叫 (initForegroundServiceManagerMessageAndIntent()initForegroundServiceManagerProvider()),請以 try-catch 區塊包圍該呼叫,以免重新輸入路徑。如果您多次呼叫任一方法,Navigation SDK 會擲回執行階段例外狀況,除非您先清除對 ForegroundServiceManager 的所有參照,並在每次後續呼叫前呼叫 clearForegroundServiceManager()

initForegroundServiceManagerMessageAndIntent() 的四個參數為 applicationnotificationIddefaultMessageresumeIntent。如果最後三個參數為空值,則通知是標準的 Navigation SDK 通知。您仍可將應用程式中的其他前景服務隱藏在這則通知後方。notificationId 參數會指定通知應使用的通知 ID。如果為空值,系統會使用任意值。您可以明確設定,解決與其他通知 (例如來自其他 SDK 的通知) 的衝突。defaultMessage 是系統未導航時顯示的字串。resumeIntent 是在點選通知時觸發的意圖。如果 resumeIntent 為空值,系統會忽略通知點擊次數。

initForegroundServiceManagerProvider() 的三個參數分別是 applicationnotificationIdnotificationProvider。如果最後兩個參數為空值,則通知是標準的 Navigation SDK 通知。notificationId 參數會指定通知應使用的通知 ID。如果為空值,系統會使用任意值。您可以明確設定這項屬性,解決與其他通知 (例如來自其他 SDK 的通知) 的衝突。如果已設定 notificationProvider,供應商一律有責任產生要顯示的通知。

Navigation SDK getForegroundServiceManager() 方法會傳回前景服務管理員單例項。如果尚未產生,則相當於呼叫 initForegroundServiceManagerMessageAndIntent(),並將 notificationIddefaultMessageresumeIntent 的參數設為空值。

ForegroundServiceManager有三種簡單方法。前兩者用於將服務移入及移出前景,通常是從已建立的服務內呼叫。使用這些方法可確保服務與共用的持續性通知相關聯。最後一個方法 updateNotification() 會向管理員標示通知已變更,應重新算繪。

如果您需要完全掌控共用的持續性通知,API 會提供 NotificationContentProvider 介面來定義通知供應商,其中包含單一方法,可取得含有目前內容的通知。此外,這個類別還提供基本類別,您可選擇使用這個類別來定義提供者。基礎類別的主要用途之一,是提供呼叫 updateNotification() 的方法,不必存取 ForegroundServiceManager。如果您使用通知供應商的例項接收新通知訊息,可以直接呼叫這個內部方法,在通知中顯示訊息。

使用情境

本節將詳細說明使用共用持續性通知的情境。

隱藏其他應用程式前景服務的持續性通知
最簡單的情況是保留目前的行為,只使用持續性通知來顯示 Navigation SDK 資訊。其他服務可以使用前景服務管理員 startForeground()stopForeground() 方法,隱藏在這個通知後方。
隱藏其他應用程式前景服務的常駐通知,但設定未導覽時顯示的預設文字
第二簡單的情境是保留目前的行為,並僅使用持續性通知來算繪 Navigation SDK 資訊,但系統未導航時除外。系統未導航時,會顯示提供給 initForegroundServiceManagerMessageAndIntent() 的字串,而不是提及「Google 地圖」的預設 Navigation SDK 字串。您也可以使用這項呼叫,設定在點選通知時觸發的繼續意圖。
完全掌控持續性通知的算繪作業
最後一個情境需要定義及建立通知供應器,並使用 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 連線至該 NotificationContentProviderImpl

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 不提供簡單的方式來擷取通知供應商可能插入通知中的逐步導航資訊。