從 Android API 級別 26 開始,前景服務必須使用持續通知。這項規定旨在避免您隱藏可能對系統資源 (特別是電池) 造成過度需求的服務。這項規定會造成潛在問題:如果應用程式有多個前景服務,但未仔細管理通知,以便在所有服務間共用,則可能會有多個無法關閉的持續通知,導致通知的有效清單中出現不必要的雜亂。
使用 Navigation SDK 等 SDK 時,這個問題會變得更棘手,因為這些 SDK 會獨立於應用程式執行前景服務,而應用程式本身也有獨立的常駐通知,因此難以整合。為解決這些問題,Navigation SDK 1.11 版推出了簡單的 API,可協助管理應用程式 (包括 SDK 內) 的持續性通知。
元件
前景服務管理員會在 Android 前景服務類別和持續通知類別周圍提供包裝函式。這個包裝函式的主功能是強制重複使用 Notification ID,以便透過管理員在所有前景服務中共用通知。
Navigation SDK 包含用於初始化及取得 ForegroundServiceManager
單例的靜態方法。這個單例在 Navigation SDK 的生命週期中只能初始化一次。因此,如果您使用其中一個初始化呼叫 (initForegroundServiceManagerMessageAndIntent()
或 initForegroundServiceManagerProvider()
),則應在該呼叫前後加上 try-catch 區塊,以防該路徑重新輸入。如果您呼叫任一方法超過一次,Navigation SDK 就會擲回執行階段例外狀況,除非您先清除所有對 ForegroundServiceManager
的參照,並在每次後續呼叫之前呼叫 clearForegroundServiceManager()
。
initForegroundServiceManagerMessageAndIntent()
的四個參數分別是 application
、notificationId
、defaultMessage
和 resumeIntent
。如果最後三個參數為空值,則通知為標準 Navigation SDK 通知。您仍可在通知後方隱藏應用程式中的其他前景服務。notificationId
參數會指定應用於通知的通知 ID。如果為空值,則會使用任意值。您可以明確設定,以便解決與其他通知 (例如其他 SDK 的通知) 發生衝突的問題。defaultMessage
是系統未導覽時顯示的字串。resumeIntent
是點選通知時觸發的意圖。如果 resumeIntent
為空值,系統會忽略通知的點擊次數。
initForegroundServiceManagerProvider()
的三個參數為 application
、notificationId
和 notificationProvider
。如果最後兩個參數為空值,則通知為標準 Navigation SDK 通知。notificationId
參數會指定應用於通知的通知 ID。如果為空值,則會使用任意值。您可以明確設定,以便解決與其他通知 (例如其他 SDK 的通知) 發生衝突的問題。如果已設定 notificationProvider
,則供應器一律會負責產生要轉譯的通知。
Navigation SDK getForegroundServiceManager()
方法會傳回前景服務管理員單例。如果您尚未產生一個,則等同於使用 notificationId
、defaultMessage
和 resumeIntent
的空參數呼叫 initForegroundServiceManagerMessageAndIntent()
。
ForegroundServiceManager
有三種簡單方法。前兩個用於將服務移入及移出前景,通常會在已建立的服務中呼叫。使用這些方法可確保服務與共用持續性通知相關聯。最後一個方法 updateNotification()
會標示通知已變更,且應重新算繪。
如果您需要完全控制共用持續性通知,API 會提供 NotificationContentProvider
介面,用於定義通知供應器,其中包含取得包含目前內容的通知的單一方法。它還提供基本類別,您可以視需要使用該類別協助定義提供者。基礎類別的主要用途之一,是提供一種方法,可在不需存取 ForegroundServiceManager
的情況下呼叫 updateNotification()
。如果您使用通知供應器的例項來接收新的通知訊息,可以直接呼叫這個內部方法,在通知中顯示訊息。
使用情境
本節將詳細說明使用共用持續通知的使用情境。
- 隱藏其他應用程式前景服務的持續通知
- 最簡單的情況是保留目前的行為,並只使用持久通知來轉譯 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 連結至該物件:
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 並未提供簡單的方法,讓通知供應商在通知中插入逐轉資訊。