共用持續通知

從 Android API 級別 26 開始,前景服務需要持續通知。這項規定旨在防止隱藏可能導致系統資源過度耗用的服務,包括電池特有的問題。這項要求會產生潛在問題:如果具有多項前景服務的應用程式未謹慎管理通知,導致其在所有服務之間共用,則會有多個無法關閉的通知,導致有效通知清單看起來雜亂無章。

當您使用的 SDK (例如 Navigation SDK) 會執行獨立於應用程式的前景服務,而這類服務具有各自獨立的永久通知,會變得更加困難。為解決這些問題,Navigation SDK 1.11 版推出了簡易 API,協助您管理應用程式 (包括 SDK 內部) 的永久通知。

整合持續性通知

元件

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


NavigationAPI 包含用於初始化及取得 ForegroundServiceManager 單例模式的靜態方法。在 Navigation SDK 的生命週期內,這個單例模式只能初始化一次。因此,如果您使用其中一個初始化呼叫 (initForegroundServiceManagerMessageAndIntent()initForegroundServiceManagerProvider()),應以 try/catch 區塊包住該路徑,以免再次進入該路徑。為避免發生不相容的問題,除非您先清除 ForegroundServiceManager 的所有參照,並在每個後續呼叫之前呼叫 clearForegroundServiceManager(),否則 Navigation SDK 會擲回執行階段例外狀況。在 2.0 版的 Navigation SDK 中,我們已在 API 中新增經過檢查的例外狀況,以便達到這個目的。

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

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

Navigation SDK 的 getForegroundServiceManager() 方法會傳回前景服務管理員單例模式。如果尚未產生,就等同於針對 notificationIddefaultMessageresumeIntent 使用空值參數呼叫 initForegroundServiceManagerMessageAndIntent()

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 "";
   }
 }
}

注意事項和未來計畫

  • 請務必盡早呼叫 initForegroundServiceManagerMessageAndIntent()initForegroundServiceManagerProvider(),以便明確定義預期的使用情境。您必須先呼叫這個方法,才能建立新的導覽器。
  • 如果程式碼通道多次輸入,請務必從呼叫 initForegroundServiceManagerMessageAndIntent()initForegroundServiceManagerProvider() 時擷取例外狀況。在 Navigation SDK 2.0 版中,多次呼叫此方法會擲回已檢查的例外狀況,而非執行階段例外狀況。
  • Google 可能還是會設法在生命週期 (符合標頭樣式) 的通知生命週期內取得一致的樣式。
  • 定義通知供應程式時,您可以透過優先順序控制抬頭行為。
  • 對於通知供應商可能會插入通知的即時路線資訊,Google 尚未提供簡單的方法。