持續性活動

在 Wear OS 中,如果您將持續性活動和持續性通知配對,系統會把該通知加到 Wear OS 使用者介面的其他途徑內。如此一來,就能提升使用者參與長時間執行活動的程度。

持續性通知通常用於表示該通知含有使用者積極參與的背景工作,這類背景工作也有可能是以某種方式處於待處理的狀態,因而占用了裝置資源。

舉例來說,Wear OS 使用者可能會使用健身應用程式記錄跑步活動的資料,接著離開該應用程式以開始執行某些其他工作。當使用者離開健身應用程式時,應用程式會轉換為繫結至部分背景工作的持續性通知,讓使用者掌握其執行相關資訊。這類通知可以讓使用者瞭解最新狀況,也能讓使用者透過輕觸輕鬆返回應用程式。

不過,如果使用者想查看通知,則須滑到錶面下方的通知匣內,然後尋找相應通知。這就沒有其他途徑來得便利。

透過 Ongoing Activity API,應用程式的持續性通知可利用 Wear OS 的多個新途徑來顯示資訊,這些途徑十分便利,可提升使用者參與度。

舉例來說,在這個健身應用程式中,資訊可在使用者的錶面上顯示為可輕觸的「跑步中」圖示:

「跑步中」圖示

圖 1. 活動指標。

全域應用程式啟動器的「Recents」部分也會列出所有持續性活動:

啟動器

圖 2. 全域啟動器。

與持續性活動繫結的持續性通知非常適合用於以下情況:

計時器

圖 3. 計時器:主動倒數計時,並在計時器暫停或停止時結束。

地圖

圖 4. 即時路線導航︰播報前往目的地的路線,在使用者抵達目的地或停止導航時結束。

音樂

圖 5. 媒體︰在工作階段全程播放音樂,當使用者暫停工作階段後立即結束。

Wear 會自動為媒體應用程式建立持續性活動。

請參閱「持續性活動程式碼研究室」,瞭解為其他類型的應用程式建立持續性活動的詳細範例。

設定

如果您想開始在應用程式中使用 Ongoing Activity API,請在應用程式的 build.gradle 檔案中加入以下依附元件:

dependencies {
  implementation "androidx.wear:wear-ongoing:1.0.0"
  // Includes LocusIdCompat and new Notification categories for Ongoing Activity.
  implementation "androidx.core:core:1.6.0"
}

開始執行持續性活動

請先建立持續性通知,再建立持續性活動。

建立持續性通知

持續性活動與持續性通知密切相關。這兩者會彼此合作,通知使用者目前正主動參與哪項工作,或有哪項工作出於某種原因而處於待處理狀態,因而占用了裝置資源。

您必須將持續性活動和持續性通知配對。建立持續性活動與通知間的連結有許多好處,包括:

  • 若裝置不支援持續性活動,可將通知當做備用方案。通知是應用程式在背景執行時顯示的唯一途徑。
  • 在 Android 11 以上版本中,若應用程式在其他途徑上顯示為持續性活動,Wear OS 會隱藏通知匣內的通知。
  • 目前的實作方式以 Notification 本身做為通訊機制。

使用 Notification.Builder.setOngoing 建立持續性通知。

開始執行持續性活動

建立持續性通知後,請參考下列範例建立持續性活動。請查看隨附的註解,瞭解各項屬性的行為。

Kotlin

var notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
      
      .setSmallIcon(..)
      .setOngoing(true)

val ongoingActivityStatus = Status.Builder()
    // Sets the text used across various surfaces.
    .addTemplate(mainText)
    .build()

val ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
        // Sets the animated icon that will appear on the watch face in
        // active mode.
        // If it isn't set, the watch face will use the static icon in
        // active mode.
        .setAnimatedIcon(R.drawable.ic_walk)
        // Sets the icon that will appear on the watch face in ambient mode.
        // Falls back to Notification's smallIcon if not set.
        // If neither is set, an Exception is thrown.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap/touch event so users can re-enter your app from the
        // other surfaces.
        // Falls back to Notification's contentIntent if not set.
        // If neither is set, an Exception is thrown.
        .setTouchIntent(activityPendingIntent)
        // Here, sets the text used for the Ongoing Activity (more
        // options are available for timers and stopwatches).
        .setStatus(ongoingActivityStatus)
        .build()

ongoingActivity.apply(applicationContext)

notificationManager.notify(NOTIFICATION_ID, builder.build())

Java

NotificationCompat.Builder notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
      
      .setSmallIcon(..)
      .setOngoing(true);

OngoingActivityStatus ongoingActivityStatus = OngoingActivityStatus.Builder()
    // Sets the text used across various surfaces.
    .addTemplate(mainText)
    .build();

OngoingActivity ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
        // Sets the animated icon that will appear on the watch face in
        // active mode.
        // If it isn't set, the watch face will use the static icon in
        // active mode.
        .setAnimatedIcon(R.drawable.ic_walk)
        // Sets the icon that will appear on the watch face in ambient mode.
        // Falls back to Notification's smallIcon if not set.
        // If neither is set, an Exception is thrown.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap/touch event so users can re-enter your app from the
        // other surfaces.
        // Falls back to Notification's contentIntent if not set.
        // If neither is set, an Exception is thrown.
        .setTouchIntent(activityPendingIntent)
        // Here, sets the text used for the Ongoing Activity (more
        // options are available for timers and stopwatches).
        .setStatus(ongoingActivityStatus)
        .build();

ongoingActivity.apply(applicationContext);

notificationManager.notify(NOTIFICATION_ID, builder.build());

下列步驟會指出上述範例中最重要的部分:

  1. NotificationCompat.Builder 上呼叫 .setOngoing(true),並設定所有非必要欄位。

  2. 建立 OngoingActivityStatus 或其他狀態選項 (如下一節所述) 來代表文字。

  3. 建立 OngoingActivity 並設定通知 ID。

  4. 利用結構定義在 OngoingActivity 上呼叫 apply()

  5. 呼叫 notificationManager.notify() 並傳入持續性活動中所設的通知 ID,以便將兩者繫結在一起。

狀態

您可以使用 Status,在新途徑 (例如起動器的「Recents」部分) 上向使用者顯示 OngoingActivity 目前的即時狀態。如要使用這項功能,請使用 Status.Builder 子類別。

在多數情況下,您只需新增範本,即可表示希望在應用程式啟動器的「Recents」部分顯示哪些文字。

然後,您可以使用 addTemplate() 方法,自訂與 span 一起顯示的文字樣式,並將文字的任何動態部分指定為 Status.Part

以下範例將展示如何讓「time」文字顯示為紅色。這個範例使用 Status.StopwatchPart 代表應用程式啟動器「Recents」部分的碼錶。

Kotlin

val htmlStatus =
        "<p>The <font color=\"red\">time</font> on your current #type# is #time#.</p>"

val statusTemplate =
        Html.fromHtml(
                htmlStatus,
                Html.FROM_HTML_MODE_COMPACT
        )

// Creates a 5 minute timer.
// Note the use of SystemClock.elapsedRealtime(), not System.currentTimeMillis().
val runStartTime = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(5)

val status = new Status.Builder()
   .addTemplate(statusTemplate)
   .addPart("type", Status.TextPart("run"))
   .addPart("time", Status.StopwatchPart(runStartTime)
   .build()

Java

String htmlStatus =
        "<p>The <font color=\"red\">time</font> on your current #type# is #time#.</p>";

Spanned statusTemplate =
        Html.fromHtml(
                htmlStatus,
                Html.FROM_HTML_MODE_COMPACT
        );

// Creates a 5 minute timer.
// Note the use of SystemClock.elapsedRealtime(), not System.currentTimeMillis().
Long runStartTime = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(5);

Status status = new Status.Builder()
   .addTemplate(statusTemplate)
   .addPart("type", new Status.TextPart("run"))
   .addPart("time", new Status.StopwatchPart(runStartTime)
   .build();

如果您想參照範本中的某個部分,請在名稱前後加上 #。如果想在輸出結果中產生 #,請在範本中使用 ##

前面的範例使用 HTMLCompat 產生傳遞到範本內的 CharSequence,這樣做比手動定義 Spannable 物件要容易得多。

其他自訂項目

除了 Status 以外,您也可以透過下列方式自訂持續性活動或通知。不過,這些自訂項目不一定會用到,具體視原始設備製造商 (OEM) 的實作方式而定。

持續性通知

  • 類別集可以判斷持續性活動的優先順序。
    • CATEGORY_CALL語音/視訊通話來電,或類似的同步通訊要求
    • CATEGORY_NAVIGATION地圖或即時路線導航
    • CATEGORY_TRANSPORT用於播放的媒體傳輸控制項
    • CATEGORY_ALARM鬧鐘或計時器
    • CATEGORY_WORKOUT健身 (新類別)
    • CATEGORY_LOCATION_SHARING臨時位置資訊分享 (新類別)
    • CATEGORY_STOPWATCH碼錶 (新類別)

持續性活動

  • 動畫圖示︰黑白向量,建議使用透明背景。此圖示會顯示在正常模式的錶面上。如未提供動畫圖示,系統會使用預設通知圖示 (每個應用程式的預設通知圖示不盡相同)。

  • 靜態圖示︰有透明背景的向量圖示,會顯示在微光模式的錶面上。如未設定動畫圖示,則正常模式的錶面會使用靜態圖示。如未提供此內容,則會使用通知圖示。如果兩者皆未設定,便會擲回例外狀況 (應用程式啟動器仍會使用應用程式圖示)。

  • OngoingActivityStatus:純文字或 Chronometer,會顯示在應用程式啟動器的「Recents」部分。如果未提供此內容,則會使用通知「內容文字」。

  • 觸控意圖︰這個 PendingIntent 用於在使用者輕觸持續性活動圖示時切換回應用程式,會顯示在錶面或啟動器項目上。這種意圖可能會與用來啟動應用程式的原始意圖不同。如未提供此意圖,系統會使用通知的內容意圖。如果兩者皆未設定,便會擲回例外狀況。

  • LocusId此 ID 用於指派持續性活動對應的啟動器捷徑。當活動持續進行時,啟動器的「Recents」部分會顯示此 ID。如未提供此 ID,啟動器會隱藏同一套件內「Recents」部分的所有應用程式項目,只顯示持續性活動。

  • 持續性活動 ID:此 ID 可在應用程式有多個持續性活動時,用來區分對 fromExistingOngoingActivity() 的呼叫。

更新持續性活動

在大多數情況下,開發人員需要更新畫面上的資料時,會建立新的持續性通知和持續性活動。不過,如果您想保留例項,而不重新建立例項,Ongoing Activity API 也能提供用於更新 OngoingActivity 的輔助方法。

如果應用程式在背景執行,可以傳送更新內容至 Ongoing Activity API。但是,採取這個做法的頻率不能過高,因為更新方法會忽略彼此太過接近的呼叫。合理的頻率是每分鐘進行幾次更新。

如要更新持續性活動和已發布的通知,請使用您之前建立的物件並呼叫 update(),如以下範例所示:

Kotlin

ongoingActivity.update(context, newStatus)

Java

ongoingActivity.update(context, newStatus);

為了方便起見,這裡使用靜態方法建立持續性活動。

Kotlin

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus)

Java

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus);

停止執行持續性活動

當應用程式的持續性活動執行完畢時,只需取消持續性通知即可。

您也可以選擇在應用程式進入前景時,取消通知或持續性活動,然後在應用程式返回背景時,重新建立上述通知和活動,但這不是硬性規定。

暫停執行持續性活動

如果應用程式有明確的停止動作,請在取消暫停後繼續執行持續性活動。至於沒有明確停止動作的應用程式,請在活動暫停時結束活動。

最佳做法

使用 Ongoing Activity API 時,請特別注意下列事項:

  • 在呼叫 notificationManager.notify(...) 之前,請先呼叫 ongoingActivity.apply(context)
  • 請為持續性活動設定靜態圖示,不論是要明確設定,還是設為透過通知使用的備用方案皆可。如未設定,就會發生 IllegalArgumentException

  • 請使用背景是透明的黑白向量圖示。

  • 請為持續性活動設定觸控意圖,不論是要明確設定,還是設為使用通知的備用方案皆可。如未設定,就會發生 IllegalArgumentException

  • 如果是 NotificationCompat,請使用 Core AndroidX 程式庫 core:1.5.0-alpha05+,其中包含 LocusIdCompat 及健身、碼錶和位置資訊分享這些新類別

  • 如果應用程式在資訊清單中宣告的 MAIN LAUNCHER 活動不只一種,請發布動態捷徑,然後使用 LocusId 將捷徑設為持續性活動的關聯資源。

在 Wear OS 裝置上播放媒體時發布媒體通知

如果媒體內容正在 Wear OS 裝置上播放, 發布媒體通知。 如此一來,系統就能建立相應的持續性活動。

如果您使用的是 Media3,系統會自動發布通知。如果發生以下情況: 手動建立通知,則應該使用 MediaStyleNotificationHelper.MediaStyle、 而對應的 MediaSession 應包含 工作階段活動