持續進行的活動

Stay organized with collections Save and categorize content based on your preferences.

在 Wear OS 內,把持續進行的活動持續進行的通知配對後,會將該通知加到 Wear OS 使用者介面的額外途徑內。這樣做能夠讓使用者更容易參與長期執行動作。

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

舉例來說,Wear OS 使用者可能會使用健身應用程式並從活動開始記錄跑步過程,然後離開該應用程式以開始執行其他工作。當使用者離開健身應用程式時,應用程式通常會轉換成與某些背景工作 (例如服務或鬧鐘管理員) 相關的持續進行的通知,讓使用者掌握跑步過程的資訊。通知可以讓使用者瞭解最新狀況,也能讓使用者透過輕觸輕鬆返回應用程式。

不過,如果使用者要查看通知,就必須滑到錶面下方的通知匣內,然後尋找適當的通知。這點不如其他途徑便利。

應用程式的持續進行的通知可透過 Ongoing Activity API 在 Wear OS 全新方便的多個介面上顯示資訊,幫助使用者持續參與。

舉例來說,在這個健身應用程式中,系統可以在使用者的錶面以可以輕觸的執行中圖示形式顯示資訊:

執行中圖示

圖 1. 活動指標

全域應用程式啟動器的「近期」區段也會列出所有持續進行的活動:

啟動器

圖 2. 全域啟動器

以下情況非常適合將持續進行的通知連接到持續進行的活動上:

計時器

圖 3. 計時器: 主動計算時間,並在計時器暫停/停止時結束。

地圖

圖 4. 即時路線導航 宣佈目的地方向。當使用者到達目的地或停止導航時結束。

音樂

圖 5. 媒體: 透過工作階段播放音樂。在使用者暫停工作階段後立即結束。

Wear 會自動為媒體應用程式建立持續進行的活動。請看 GitHub 的「持續進行的活動程式碼研究室」,查看為其他種類的應用程式建立持續進行的活動的詳細範例。

設定

如果想在應用程式中使用 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 當做通訊機制。

持續進行的活動

只要有了持續進行的通知,就能輕鬆啟動持續進行的活動。

以下程式碼範例內含有註解,可以幫助您瞭解每個屬性的代表含意:

Kotlin

var builder = 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)
        // In our case, 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 builder = 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)
        // In our case, 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 可讓開發人員向使用者在新途徑上顯示 OngoingActivity 的即時狀態,如起動器的「近期」區段。如果想使用這項功能,請用 Status.Builder

在大多數情況下,開發人員只需要新增範本代表想在應用程式啟動器「近期」區段顯示的文字即可。

開發人員可以使用 addTemplate() 方法自訂和 Spans 一起顯示的文字樣式,並將文字的任何動態部分指定為 Status.Part

以下範例將展示如何讓「time」文字顯示為紅色。此範例使用 Status.TimerPart,我們可以藉此代表計時器或 Status.StopwatchPart,在應用程式啟動器的「近期」區段中表示碼表。

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:純文字或計時器。會顯示在應用程式啟動器的「近期」區段內。如果未提供此內容,則會使用通知「內容文字」。

  • 觸控意圖:如果使用者輕觸持續進行的活動圖示,便會使用 PendingIntent 切換回應用程式。會顯示在錶面或啟動器項目上。這種意圖可以不同於用來啟動應用程式的原始意圖。如果未提供這種意圖,則會使用通知的內容意圖,如果兩者都並未設定,便會擲回例外狀況。\

  • LocusId:指派和持續進行的活動相關的啟動器快速指令用的 ID:當活動持續進行時,會顯示在啟動器的「近期」區段內。如果未提供此內容,啟動器會在「近期」區段隱藏相同套件內的所有應用程式項目,只顯示持續進行的活動。\

  • 持續進行的活動 ID 是當應用程式有複數持續進行的活動時,用來區分 fromExistingOngoingActivityfromExistingOngoingActivity() 呼叫的 ID。

更新持續進行的活動

當需要更新畫面資料時,開發人員多半都會建立新的持續進行的通知,以及新的持續進行的活動。不過,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 時,請特別注意下列事項:

  • 務必先呼叫 ongoingActivity.apply(context) 再呼叫 notificationManager.notify(...)
  • 持續進行的活動請務必設定靜態圖示,您可以明確設定也可以透過通知當做備用方案。如果並未設定,就會發生 IllegalArgumentException

  • 圖示必須為黑白向量,並使用透明背景。

  • 請務必為持續進行的活動設定觸控意圖,您可以明確設定也可以透過通知當做備用方案。如果並未設定,就會發生 IllegalArgumentException

  • NotificationCompat 請用 core androidx 程式庫 core:1.5.0-alpha05+,其中含有 LocusIdCompat新類別 (健身、碼表或位置資訊分享)。

  • 如果應用程式在資訊清單當中有宣告多種 MAIN LAUNCHER 活動,請發布動態捷徑,然後使用 LocusId 把捷徑連結到持續進行的活動上。