使用 Android 通知

這個程式碼研究室是「Android Kotlin 進階功能」課程的一部分。如果您按部就班完成每一堂程式碼研究室課程,就能充分體驗到本課程的價值,但這不是強制要求。如要查看所有課程程式碼研究室,請前往 Android Kotlin 進階功能程式碼研究室登陸頁面

簡介

通知是指在應用程式 UI 以外顯示給使用者的訊息。如果裝置已解鎖,通知會顯示在螢幕頂端;如果裝置已鎖定,通知會顯示在螢幕鎖定畫面上 (視安全性設定而定)。

一般通知包含標題、說明和圖示。通知也可以包含可點選的操作、快速回覆、可展開的內容和圖片。

通知可即時傳送重要資訊,並提供按鈕讓使用者快速執行動作,例如傳送回覆或延後鬧鐘。使用者點選通知後,系統會將他們帶往應用程式中與通知內容相關的檢視畫面。

當應用程式在背景執行時,通知可提醒使用者重要工作、告知使用者發生了某件事,或傳達使用者需要立即瞭解的重要資訊。請盡量減少使用通知。這麼做不僅尊重使用者,也有助於應用程式通知獲得應有的關注。

在本程式碼研究室中,您將瞭解如何在 Android 應用程式中建立及使用通知。

必備知識

您必須已經熟悉下列項目:

  • 如何使用 Kotlin 建立 Android 應用程式。特別是 Android SDK。
  • 如何使用架構元件和資料繫結設計應用程式架構。
  • 對 BroadcastReceiver 有基本瞭解
  • 對 AlarmManager 的基本概念

課程內容

  • 如何建立、設定樣式及傳送通知。
  • 如何取消通知。
  • 如何建立通知管道。
  • 如何將快速動作新增至通知。
  • 如何在應用程式圖示上顯示通知標記。

學習內容

  • 在入門應用程式中新增通知。
  • 取消先前傳送的通知。
  • 為不同類型的通知建立管道。
  • 在入門應用程式中自訂通知。
  • 新增快速操作,讓通知具有互動性。
  • 關閉通知標記。

煮蛋很簡單,但如果沒有掌握時間,可能會煮出不滿意的成品。在本程式碼研究室中,您將建構並完善計時器應用程式,就像您未來煮出來的雞蛋一樣完美。您將從可運作的蛋計時器應用程式開始,讓使用者為不同熟度的蛋設定不同的烹調時間。計時器會從選取的時間間隔開始倒數,並在雞蛋煮好時顯示浮動式訊息。

這看起來似乎可行,但遠遠不夠完美,而且不夠友善。首先,快訊訊息只會短暫顯示,因此很容易錯過。此外,如果應用程式不在前景或裝置已鎖定,快訊訊息消失後,就不會顯示計時器的狀態。

理想情況下,計時器應使用通知功能,在時間到時通知使用者。使用者必須立即知道雞蛋是否煮熟,否則雞蛋就會煮過頭!通知會以視覺化方式呈現,可能包含音效,也可能讓裝置震動,藉此吸引使用者注意。這樣就能做出完美的雞蛋,讓使用者吃得開心又滿足。

如要取得範例應用程式,請執行下列任一動作:

從 GitHub 複製存放區,然後切換至 starter 分支。

$  git clone https://github.com/googlecodelabs/android-kotlin-notifications


或者,您也可以將存放區下載為 ZIP 檔案、將其解壓縮,並在 Android Studio 中開啟。

下載 ZIP 檔

  1. 在 Android Studio 中開啟並執行應用程式。

畫面上會顯示雞蛋圖片和下拉式選單,內含預先定義的雞蛋烹調時間間隔清單。按一下「Soft Boiled」下拉式選單的三角形,清單中的第一個選項是為了測試用途,會將鬧鐘設為 10 秒。清單旁邊的切換按鈕可啟動計時器。你可以隨時使用這個切換鈕啟動和停止計時器。範例程式碼可以正常運作,也就是說,您可以設定計時器,並看著它倒數到 0。計時器結束後,系統會顯示下列訊息。

  1. 檢查原始碼。範例應用程式是由名為 MainActivity 的單一活動所組成。有三個子套件,分別名為 receiveruiutil

  • /receiverreceiver 套件包含兩個名為 AlarmReceiverSnoozeReceiver 的廣播接收器。AlarmReceiver會在使用者定義的計時器時間到時,觸發 AlarmManager 傳送通知。SnoozeReceiver 會處理使用者點選延後通知的操作。
  • /ui:包含 EggTimerFragment,這是應用程式 UI 部分的元件。EggTimerViewModel 負責啟動及取消計時器,以及其他與生命週期相關的應用程式工作。
  • /util:這個套件中有兩個檔案。BindingUtils.kt 具有繫結轉接器,可在應用程式 UI 和 ViewModel 之間啟用資料繫結。NotificationUtils.kt 具有 NotificationManager 的擴充方法。

通知是吸引使用者注意應用程式的絕佳方式。無論應用程式是否正在執行或在前台執行,通知都會在畫面上方顯示彈出式視窗,並可能包含音效或震動。如要建立通知,請使用通知建構工具,並提供標題文字、內容文字和圖示。建構工具備妥所有必要欄位後,系統服務 NotificationManager 會協助您將這項內容顯示為通知。NotificationManager 負責傳送通知、更新內容及取消通知。在接下來的步驟中,您將擴充方法新增至 NotificationManager。這樣一來,每當需要使用 NotificationManager 時,您就能使用這些擴充功能函式來達成所需功能。

步驟 1:建立基本通知

在這項工作中,您將建立新通知、為使用者設定訊息,並傳送通知

  1. 開啟 NotificationUtils.kt 類別,然後找到 TODO: Step 1.1。您會在程式碼研究室和應用程式程式碼中找到相符的待辦事項。
  2. 檢查指定的 sendNotification() 函式。您將擴充這個擴充功能函式,以傳送通知。NotificationManager
//NotificationUtils.kt
// TODO: Step 1.1 extension function to send messages (GIVEN)
/**
 * Builds and delivers a notification.
 *
 * @param messageBody, notification text.
 * @param context, activity context.
 */
fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
  1. 取得通知建構工具的例項,並傳入應用程式內容和管道 ID。管道 ID 是管道的字串值。

通知管道可將通知分組。開發人員和使用者可以將類似的通知類型分組,並控管管道中的所有通知。建立管道後,即可用於傳送任意數量的通知。

//NotificationUtils.kt
// TODO: Step 1.2 get an instance of NotificationCompat.Builder
val builder = NotificationCompat.Builder(
        applicationContext,
        applicationContext.getString(R.string.egg_notification_channel_id)
)
  1. 設定通知圖示來代表應用程式、標題,以及要提供給使用者的訊息內容文字。您會在程式碼研究室中看到更多自訂通知的選項,但這是傳送通知時必須設定的最低資料量。
//NotificationUtils.kt
   // TODO: Step 1.3 set title, text and icon to builder
   .setSmallIcon(R.drawable.cooked_egg)
   .setContentTitle(applicationContext.getString(R.string.notification_title))
   .setContentText(messageBody)
  1. 接著,您需要使用通知的專屬 ID 和建構工具中的 Notification 物件呼叫 notify()

這個 ID 代表目前的通知例項,更新或取消通知時需要用到。由於應用程式在特定時間只會有一則有效通知,因此您可以為所有通知使用相同 ID。NotificationUtils.kt 中已提供名為 NOTIFICATION_ID 的常數,可供您使用。請注意,由於您是從相同類別的擴充功能函式執行呼叫,因此可以直接呼叫 notify()

//NotificationUtils.kt
   // TODO: Step 1.4 call notify to send the notification
    // Deliver the notification
    notify(NOTIFICATION_ID, builder.build())
  1. 開啟 ui/EggTimerViewModel.kt 並找出 startTimer() 函式。使用者啟用計時器時,這項函式會根據所選時間間隔建立鬧鐘。
  2. 使用者啟動計時器時,您會在函式中觸發通知。如要呼叫先前實作的 sendNotification() 函式,您需要 NotificationManager 的執行個體。NotificationManager 是一項系統服務,可提供通知 API 的所有公開函式,包括您新增的擴充功能函式。如要傳送、取消或更新通知,隨時都需要向系統要求 NotificationManager 的執行個體。使用通知訊息和內容呼叫 sendNotification()| 函式。
// EggTimerViewModel.kt
// TODO: Step 1.5 get an instance of NotificationManager 
// and call sendNotification

val notificationManager = ContextCompat.getSystemService(
    app, 
    NotificationManager::class.java
) as NotificationManager
                notificationManager.sendNotification(app.getString(R.string.timer_running), app)

即將完成。不過,如果您現在執行應用程式並設定計時器,就不會收到通知。

  1. 開啟 logcat 並搜尋 "No Channel found"。您應該會看到 egg_channel 不存在的錯誤訊息。在後續步驟中,您將進一步瞭解通知管道並修正這個問題。

步驟 2:通知管道

從 API 級別 26 開始,所有通知都必須指派給某個管道。按住應用程式啟動器圖示,選取應用程式資訊,然後輕觸「通知」,您會看到與應用程式相關聯的通知管道清單。目前清單是空白的,因為您的應用程式尚未建立任何管道。

頻道代表「類型」的通知,舉例來說,雞蛋煮熟時,雞蛋計時器可以傳送通知,也可以使用另一個頻道傳送每日通知,提醒你早餐要吃雞蛋。系統會將管道中的所有通知歸為一組,使用者可以為整個管道設定通知。使用者可根據感興趣的通知類型,自訂通知設定。舉例來說,使用者可以停用早餐通知,但仍選擇查看計時器通知。

開發人員會設定初始設定、重要性和行為,並套用至管道中的所有通知。設定初始設定後,使用者可以覆寫這些設定。

在步驟 1.1 中,您使用了 egg_notification_channel_id 做為通知管道,因此現在需要實際建立及自訂這個管道的通知設定和行為。

  1. 開啟 EggTimerFragment.kt 並找出 createChannel() 函式。
  2. 將專屬頻道 ID 傳遞至 NotificationChannel 的建構函式。
  3. 傳遞通知管道名稱,使用者也會在「設定」畫面上看到這個名稱。
  4. 最後一個參數請傳遞通知管道的重要程度。本程式碼研究室稍後會介紹重要性等級,因此目前可以使用 NotificationManager.IMPORTANCE_LOW
  5. notificationChannel 物件上,將 enableLights 設為 true。啟用這項設定後,裝置會在顯示通知時亮燈。
  6. notificationChannel 物件上,將 lightColor 設為紅色,以便在顯示通知時顯示紅燈。
  7. notificationChannel 物件上,將 enableVibration 設為 true,即可啟用震動功能。
  8. notificationChannel 物件上,將頻道說明設為 ‘Time for breakfast'。
  9. 呼叫 getSystemService() 即可取得 NotificationManager 的執行個體。
  10. NotificationManager 上呼叫 createNotificationChannel(),並傳遞您在上一個步驟中建立的 notificationChannel 物件。
//EggTimerFragment.kt
private fun createChannel(channelId: String, channelName: String) {
    // TODO: Step 1.6 START create a channel
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val notificationChannel = NotificationChannel(
            channelId,
            channelName,
            // TODO: Step 2.4 change importance
            NotificationManager.IMPORTANCE_LOW
        )
        // TODO: Step 2.6 disable badges for this channel

        notificationChannel.enableLights(true)
        notificationChannel.lightColor = Color.RED
        notificationChannel.enableVibration(true)
        notificationChannel.description = "Time for breakfast"

        val notificationManager = requireActivity().getSystemService(
            NotificationManager::class.java
        )
        notificationManager.createNotificationChannel(notificationChannel)
    }
    // TODO: Step 1.6 END create channel
}
  1. 接著,如要建立管道,您需要呼叫剛才編寫的 createChannel() 函式 (步驟 1.7)。這個函式會採用兩個參數:頻道 ID 和頻道名稱。您需要從專案中已提供的字串資源,查詢頻道 ID 和頻道名稱。
// EggTimerFragment.kt
    // TODO: Step 1.7 call createChannel
    createChannel(
          getString(R.string.egg_notification_channel_id),
          getString(R.string.egg_notification_channel_name)
    )
  1. 您需要將管道 ID 傳遞至通知建構工具。您已在步驟 1.2 中完成這項操作。如果將錯誤的值設為管道 ID,通知就會傳送失敗。開啟 NotificationUtils.kt,確認先前設定的頻道 ID 正確無誤。
// NotificationUtils.kt
val builder = NotificationCompat.Builder(
        applicationContext,
       // TODO: Step 1.8 verify the notification channel name
        applicationContext.getString(R.string.egg_notification_channel_id)
)
  1. 執行應用程式,您會看到應用程式在每次啟動計時器時傳送通知。
  2. 拉下狀態列,確認通知標題、內容和圖示與您在上一個步驟中設定的相同。
  3. 如要驗證新建立的頻道,請關閉應用程式並找到應用程式圖示。長按應用程式圖示,然後選取「應用程式資訊」

  1. 從設定清單中選取「通知」。「顯示通知」設定下方應該會顯示名為「Egg」的新管道。

執行應用程式時,系統現在會顯示通知。應用程式開發人員和使用者都能自訂透過這個管道傳送的所有通知設定和行為。恭喜,您已建立通知!

步驟 3:為應用程式新增通知

目前為止,我們已瞭解通知 API 的基本用法,但啟動計時器後立即傳送通知並無多大意義。使用者可能比較希望在雞蛋煮好時收到通知。在程式碼研究室的下一部分,您將修正這個問題,並將 Toast 訊息變更為通知。

您已傳送通知並觀察通知向使用者顯示的方式,但這只是建立優質通知的第一步。在這個步驟中,您將變更通知的傳送時間,使其更為適當。

應用程式會使用 AlarmManager 設定鬧鐘。範例程式碼中已提供與 AlarmManager 相關的程式碼,並用於顯示浮動式訊息。AlarmManager 會追蹤所需的時間選取項目,並在時間到時觸發 AlarmReceiver.ktonReceive() 函式。開啟 AlarmReceiver.kt 並前往 onReceive(),您應該會看到每次設定計時器時顯示的訊息。

  1. 開啟 AlarmReceiver.kt (NotificationManager 的執行個體),然後使用訊息文字和內容參數呼叫 sendNotification() 函式。
// AlarmReceiver.kt
   // TODO: Step 1.9 add call to sendNotification
   val notificationManager = ContextCompat.getSystemService(
       context, 
       NotificationManager::class.java
   ) as NotificationManager
             
   notificationManager.sendNotification(
       context.getText(R.string.eggs_ready).toString(), 
       context
   )
  1. (選用) 移除 Toast,因為應用程式會在計時器響起時傳送通知。
// AlarmReceiver.kt
     // TODO: Step 1.10 [Optional] remove toast
//   Toast.makeText(
//       context, 
//       context.getText(R.string.eggs_ready),
//       Toast.LENGTH_SHORT
//   ).show()
  1. 執行應用程式。每次啟動計時器和計時器響鈴時,您都會收到通知。

這並非理想做法。您不想傳送過多通知給使用者。你可以移除使用者啟動計時器時傳送的第一則通知。

  1. 開啟 EggTimerFragment.kt,然後移除步驟 1.5 的通知程式碼。
// EggTimeViewModel.kt

// TODO: Step 1.5 get an instance of NotificationManager 
// and call sendNotification
// val notificationManager = ContextCompat.getSystemService(
//      app,
//      NotificationManager::class.java
// ) as NotificationManager
// notificationManager.sendNotification(app.getString(R.string.eggs_ready), app)
  1. 再次執行應用程式。
  2. 設定計時器,然後將其移至背景,等待時間結束。系統會顯示通知。這項通知實用多了。

步驟 4:新增內容意圖

  1. 如果應用程式尚未執行,請再次執行。
  2. 按一下通知。此按鈕沒有任何作用,

顯示通知並告知使用者是很好的做法,但使用者點按通知時,會希望返回對應的應用程式。在本程式碼研究室的這部分,您將在通知中新增意圖,讓使用者返回計時器畫面。

Intent 是訊息物件,可用於向其他應用程式元件要求動作。意圖可用於啟動活動、服務或傳送廣播。在此情況下,您可以使用這項意圖,在使用者輕觸通知時告知系統開啟 MainActivity。由於您的應用程式只包含單一檢視畫面,因此這裡沒有太多選項。不過,如果應用程式較大,通知應將使用者帶往適合與通知互動的畫面,提供流暢體驗。

  1. 開啟 NotificationUtils.kt,然後找到 sendNotification() 擴充功能函式。
  2. 使用 applicationContext 和要啟動的活動 MainActivity::class.java 建立 Intent
// NotificationUtils.kt

fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
    // Create the content intent for the notification, which launches
    // this activity
   // TODO: Step 1.11 create intent
    val contentIntent = Intent(applicationContext, MainActivity::class.java)

您已建立意圖,但通知顯示在應用程式外部。如要讓意圖在應用程式外部運作,您需要建立新的 PendingIntent

PendingIntent 授予其他應用程式或系統權限,代表您的應用程式執行作業。PendingIntent 本身只會參照系統維護的權杖,說明用於擷取資料的原始資料。也就是說,即使擁有應用程式的程序遭到終止,PendingIntent 本身仍可供其他程序使用。在這種情況下,無論計時器應用程式是否正在執行,系統都會使用待處理意圖代表您開啟應用程式。

  1. 使用 applicationContextNOTIFICATION_ID、上一步建立的 contentIntentPendingIntent 標記建立 PendingIntentPendingIntent 旗標會指定要建立新的 PendingIntent 或使用現有 PendingIntent。您需要將 PendingIntent.FLAG_UPDATE_CURRENT 設為旗標,因為您不希望在現有通知存在時建立新通知。這樣一來,您就能修改與所提供意圖相關聯的目前 PendingIntent
// NotificationUtils.kt
   // TODO: Step 1.12 create PendingIntent
    val contentPendingIntent = PendingIntent.getActivity(
        applicationContext, 
        NOTIFICATION_ID,
        contentIntent,
        PendingIntent.FLAG_UPDATE_CURRENT
    )
  1. PendingIntent 傳遞至通知。方法是在 NotificationBuilder 上呼叫 setContentIntent()。現在點選通知時,系統會觸發 PendingIntent,開啟 MainActivity
  2. 同時將 setAutoCancel() 設為 true,這樣使用者輕觸通知時,通知就會自行關閉,並將使用者帶往應用程式。
// NotificationUtils.kt
    // TODO: Step 1.13 set content intent
    .setContentIntent(contentPendingIntent)
    .setAutoCancel(true)
  1. 再次執行應用程式。
  2. 設定計時器,將應用程式移到背景,然後等待通知顯示。
  3. 看到通知後,請下拉狀態列並點按通知,觀察應用程式如何帶到前景。

步驟 5:取消通知

您已建立可發出通知的實用計時器,但仍有小問題。如果你設定計時器、收到通知,然後再次設定計時器,狀態列會保留先前的通知,同時執行新的計時器。如果應用程式在背景執行,這可能會造成使用者混淆,而且雞蛋可能煮不熟。

如要修正這個問題,請在啟動新計時器時清除先前的通知。首先,請在 NotificationUtils.kt 中建立另一個擴充功能函式。NotificationManager 有一個 API 可取消所有有效通知,稱為 cancelAll()

  1. 開啟 NotificationsUtil.kt
  2. NotificationManager 上新增擴充功能函式,該函式會呼叫 cancelAll()
// NotificationUtils.kt

// TODO: Step 1.14 Cancel all notifications
/**
 * Cancels all notifications.
 *
 */
fun NotificationManager.cancelNotifications() {
    cancelAll()
}
  1. 開啟 EggTimerViewModel.kt,然後前往 startTimer() 函式。
  2. startTimer() 中,從系統取得 NotificationManager 的執行個體,然後呼叫 cancelNotifications()
//  EggTimerViewModel.kt
   //TODO Step 1.15 call cancel notification
    val notificationManager =
       ContextCompat.getSystemService(
            app,
            NotificationManager::class.java
        ) as NotificationManager
    notificationManager.cancelNotifications()       
  1. 執行應用程式並啟動計時器。
  2. 看到通知後,請再次啟動計時器,並觀察應用程式如何自動從狀態列刪除先前的通知。

通知架構提供多種自訂選項,開發人員可視需要設定自訂動作和通知樣式。在這項工作中,您將瞭解如何自訂蛋定時器通知。

步驟 1:設定通知樣式

根據需求和通知內容設定通知樣式,讓通知更加顯眼,看起來更像是應用程式的延伸功能。通知架構內建多種樣式可供使用,您也可以自行建立樣式。

NotificationCompat 提供下列內建樣式:

  • BigTextStyle,展開後可顯示大量文字,例如電子郵件內容。
  • BigPictureStyle,顯示包含大型圖片附件的大型格式通知。
  • InboxStyle,顯示對話風格的文字內容。
  • MediaStyle,顯示媒體播放控制項。
  • MessagingStyle,可顯示大型格式通知,內含任意人數之間的訊息。

如要進一步瞭解其他樣式,請參閱「建立可展開通知」說明文件。在本步驟中,您將使用 NotificationCompat.BigPictureStyle 建立可展開式通知,展開時會顯示大型雞蛋圖片。

  1. 開啟 NotificationUtils.kt 並找出 sendNotification() 函式。
  2. 首先,請使用 BitmapFactoryresources 載入圖片。
// NotificationUtils.kt

// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
     applicationContext.resources, 
     R.drawable.cooked_egg
)
  1. 建立新的 BigPictureStyle 並設定圖片。
  2. bigLargeIcon() 設為 null,這樣展開通知時,大型圖示就會消失。
// NotificationUtils.kt

// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
     applicationContext.resources, 
     R.drawable.cooked_egg
)
val bigPicStyle = NotificationCompat.BigPictureStyle()
        .bigPicture(eggImage)
        .bigLargeIcon(null)
  1. 使用 setStyle() 將樣式設為 bigPicStyle
  2. 將大型圖示設為 eggImagesetLargeIcon(),這樣通知收合時,圖片就會顯示為較小的圖示。
// NotificationUtils.kt
// TODO: Step 2.1 add style to builder
.setStyle(bigPicStyle)
.setLargeIcon(eggImage)
  1. 執行應用程式並設定計時器。通知首次顯示時,會以收合狀態出現在通知導覽匣中。展開通知後,擴展通知區域會顯示大型圖片。

步驟 2:通知動作

通知動作是另一種可新增至通知的自訂項目。目前使用者點選通知時,系統會將他們重新導向至應用程式。除了這項預設的通知動作之外,您也可以新增動作按鈕,以便使用者在通知中完成與應用程式相關的工作。

通知最多可提供三個動作按鈕,讓使用者快速回應,例如延後提醒或回覆簡訊。這些動作按鈕不應重複使用者輕觸通知時執行的動作。

如要新增動作按鈕,請將 PendingIntent 傳遞至建構工具上的 addAction() 函式。這與呼叫 setContentIntent() 設定通知的預設輕觸動作類似,但您可以執行各種其他動作,例如啟動在背景執行工作的 BroadcastReceiver,這樣動作就不會中斷已開啟的應用程式。

在本程式碼研究室中,您已獲得名為 SnoozeReceiverBoadcastReceiver。您將使用 SnoozeReceiver 接收使用者點選通知動作。在後續步驟中,您將新增程式碼,讓使用者點選「暫緩」動作按鈕時,系統會暫緩顯示雞蛋計時器通知 60 秒。點選「暫緩」動作後,SnoozeReceiver 會收到意圖,並建立新鬧鐘,在 60 秒後傳送新通知。

  1. 開啟 SnoozeReceiver.kt。這個類別與您先前使用的 AlarmReceiver 類似。在下列步驟中,您將新增程式碼,觸發 SnoozeReceiveronReceive() 函式。簡而言之,SnoozeReceiver 中的程式碼會建立新的鬧鐘,在一分鐘後傳送新的通知。向下捲動至 onReceive 函式底部,從系統取得 notificationManager 的例項,然後呼叫 cancelAll。
// SnoozeReceiver.kt
        val notificationManager = ContextCompat.getSystemService(
            context,
            NotificationManager::class.java
        ) as NotificationManager
        notificationManager.cancelAll()
  1. 如要使用 SnoozeReceiver,請開啟 NotificationUtils.kt
  2. sendNotification() 函式中的樣式後方,建立新的 Intent snoozeIntent,用於 SnoozeReceiver
  3. PendingIntent 上呼叫 getBroadcast() 方法,建立待處理意圖,該方法會預期下列步驟中的參數。系統會使用這個 PendingIntent 設定新鬧鐘,在使用者輕觸貪睡按鈕後 60 秒發布新通知。
  4. 第一個參數是應用程式環境,PendingIntent 會在這個環境中啟動活動。
  5. 第二個參數是要求代碼,也就是這個待處理意圖的要求代碼。如要更新或取消這個待處理意圖,您需要使用這段程式碼存取待處理意圖。
  6. 接著,新增 snoozeIntent 物件,也就是要啟動的活動意圖。
  7. 最後,由於意圖只會使用一次,因此請新增 #FLAG_ONE_SHOT 的旗標值。輕觸一次後,快速動作和通知就會消失,因此意圖只能使用一次。
// NotificationUtils.kt

// TODO: Step 2.2 add snooze action
val snoozeIntent = Intent(applicationContext, SnoozeReceiver::class.java)
val snoozePendingIntent: PendingIntent = PendingIntent.getBroadcast(
    applicationContext, 
    REQUEST_CODE, 
    snoozeIntent, 
    FLAGS
)
  1. 接著,在 notificationBuilder 上呼叫 addAction() 函式。這個函式需要圖示和文字,向使用者說明動作。您也需要新增 snoozeIntent。點選動作時,系統會使用這個意圖觸發正確的 boadcastReceiver
// NotificationUtils.kt
// TODO: Step 2.3 add snooze action
.addAction(
    R.drawable.egg_icon, 
    applicationContext.getString(R.string.snooze),
    snoozePendingIntent
)
  1. 執行蛋形計時器應用程式,測試貪睡動作。
  2. 啟動計時器,然後將應用程式移至背景。計時器響鈴後,展開通知,你會看到通知現在有「延後」動作按鈕,可將計時器延後一分鐘。

步驟 3:通知重要性

重要性會決定通知在視覺和聽覺上對使用者的干擾程度。重要性較高的通知對使用者的干擾程度也較高。

您必須在 NotificationChannel 建構函式中指定重要程度。您原本為計時器應用程式設定了低重要性。您可以使用五個重要性等級,範圍從 IMPORTANCE_NONE(0)IMPORTANCE_HIGH(4)。您為管道指派的重要性等級,會套用至您發布的所有通知訊息。

頻道重要性等級

使用者可見的優先順序等級

重要性 (Android 8.0 以上版本)

優先 (Android 7.1 以下版本)

發出音效,並顯示為抬頭通知 (在螢幕頂端彈出)

IMPORTANCE_HIGH

PRIORITY_HIGH / PRIORITY_MAX

發出音效

IMPORTANCE_DEFAULT

PRIORITY_DEFAULT

沒有聲音

IMPORTANCE_LOW

PRIORITY_LOW

不會發出音效,也不會顯示在狀態列中

IMPORTANCE_MIN

PRIORITY_MIN

如要瞭解如何選擇適當的優先順序層級,請參閱通知設計指南中的「優先順序層級」。為應用程式中的通知選取重要程度時,請務必謹慎。選擇管道重要程度時,應考量使用者的時間和注意力。如果將不重要的通知偽裝成緊急通知,可能會產生不必要的警報,讓人分心。使用者可以完全控管通知的重要性等級,因此如果您建立的通知令人反感,他們可以完全關閉您的通知管道。

您在步驟 1.6 中首次建立通知時,由於設計目的是不要以通知打擾使用者,因此系統將計時器設為傳送低優先順序的通知。不過,在雞蛋煮過頭之前吸引使用者注意,或許是個好主意。如要變更通知的重要性等級,請先調整管道設定。管道重要性會影響管道中所有通知的中斷程度,且必須在 NotificationChannel 建構函式中指定。

  1. 如要變更應用程式通知管道的重要性等級,請開啟 EggTimerFragment.kt 並前往 createChannel()。將重要程度從 IMPORTANCE_LOW 變更為 IMPORTANCE_HIGH
// EggTimerFragment.kt
    val notificationChannel = NotificationChannel(
        channelId,
        channelName,
        // TODO: Step 2.4 change importance
        NotificationManager.IMPORTANCE_HIGH
    )

如要支援搭載 Android 7.1 (API 級別 25) 以下版本的裝置,您也必須針對每則通知呼叫 setPriority(),並使用 NotificationCompat 類別中的優先順序常數。

  1. 開啟 NotificationUtils.kt,並將下列項目新增至通知建立工具物件。
// NotificationUtils.kt
   .addAction(
       R.drawable.common_google_signin_btn_icon_dark,
       applicationContext.getString(R.string.snooze),
       snoozePendingIntent
    )
   // TODO: Step 2.5 set priority
    .setPriority(NotificationCompat.PRIORITY_HIGH)
  1. 執行應用程式前,請長按裝置或模擬器上的應用程式圖示,然後選取解除安裝,清除先前的頻道設定。如果無法解除安裝應用程式,頻道優先順序設定就不會變更,因此發布通知時不會有任何行為變更。
  2. 現在再次執行應用程式,然後啟動計時器。這次傳送通知時,無論應用程式是在前景或背景執行,畫面頂端都應該會顯示彈出式視窗。

步驟 4:通知徽章

通知標記是小圓點,當應用程式有未讀通知時,就會顯示在相關聯的啟動器圖示上。使用者可以長按應用程式圖示來查看通知。

這些點 (稱為徽章) 預設會顯示,應用程式不需要執行任何動作。不過,在某些情況下,通知不適合顯示徽章,因此您可以呼叫 NotificationChannel 物件上的 setShowBadge(false),依管道停用徽章。由於雞蛋計時器在特定時間只會顯示一則有效通知,因此應用程式圖示上的徽章對使用者來說沒有太大用處。在接下來的步驟中,您將停用徽章,只顯示雞蛋計時器的通知。

  1. 在雞蛋計時器的管道建立程式碼中加入 setShowBadge(false),即可停用徽章。
// EggTimerFragment.kt

    ).apply {
        // TODO: Step 2.6 disable badges for this channel
        setShowBadge(false)
    }
  1. 再次執行應用程式,啟動計時器並觀察應用程式圖示。應用程式圖示上不應顯示任何徽章。

解決方案程式碼位於下載程式碼的主要分支

Udacity 課程:

Android 開發人員說明文件:

如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 進階功能程式碼研究室登陸頁面。