Android Notification 本地通知與推播實戰

通知 (Notification) 是 App 在 UI 介面之外,與使用者進行非同步互動的核心管道。無論是提醒訂單狀態、顯示訊息摘要,還是呈現背景任務進度,良好的通知設計能顯著提升用戶留存。

Android 13+ 權限規範

從 Android 13 (API 33) 開始,發送通知正式列入危險權限 (Dangerous Permission)。App 必須主動向使用者請求權限,且預設是不允許的。

Manifest 宣告

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

動態請求 (Compose)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    val permissionLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) { /* 發送通知 */ }
    }
    
    Button(onClick = { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }) {
        Text("允許通知")
    }
}

通知頻道 (Notification Channels)

從 Android 8.0 (API 26) 開始,所有通知都必須分配到一個「頻道」。頻道允許使用者細粒度地控制通知行為(如:開啟物流通知,但關閉行銷通知)。

fun createNotificationChannel(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(
            "ORDER_UPDATES", // Channel ID
            "訂單更新",       // 使用者在系統設定看到的名稱
            NotificationManager.IMPORTANCE_HIGH // 重要程度 (影響是否彈出或有聲音)
        ).apply {
            description = "追蹤您的商品寄送狀態"
        }
        
        val manager = context.getSystemService(NotificationManager::class.java)
        manager.createNotificationChannel(channel)
    }
}

發送基礎通知與安全轉向

使用 NotificationCompat.Builder 來構建內容,並透過 PendingIntent 定義點擊後的行為。

fun sendOrderNotification(context: Context) {
    // 點擊後開啟 Activity
    val intent = Intent(context, MainActivity::class.java)
    val pendingIntent = PendingIntent.getActivity(
        context, 0, intent,
        PendingIntent.FLAG_IMMUTABLE // Android 12+ 強制要求指定 Mutability
    )

    val builder = NotificationCompat.Builder(context, "ORDER_UPDATES")
        .setSmallIcon(R.drawable.ic_delivery) // 必要:小圖示 (通常為全白背景透明)
        .setContentTitle("包裹已送達!")
        .setContentText("您的訂單 #12345 已經抵達指定門市。")
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setContentIntent(pendingIntent)
        .setAutoCancel(true) // 點擊後自動從狀態列移除

    NotificationManagerCompat.from(context).notify(1001, builder.build())
}

進階通知樣式 (Styles)

長文本 (BigTextStyle)

當內容字數較多時,使用 BigTextStyle 允許使用者展開閱讀完整內容。

.setStyle(NotificationCompat.BigTextStyle()
    .bigText("這裡是非長的詳細內容說明,當使用者將通知向下拉開時,可以看到這一段完整的文字訊息內容..."))

圖片通知 (BigPictureStyle)

用於顯示宣傳美圖或截圖。

.setStyle(NotificationCompat.BigPictureStyle()
    .bigPicture(myBitmap)
    .bigLargeIcon(null as Bitmap?)) // 展開後隱藏大圖示,避免重複

互動與進度追蹤

動作按鈕 (Action Buttons)

你可以直接在通知上增加按鈕(最多 3 個),省去使用者手動切換 App 的麻煩。

val actionIntent = PendingIntent.getBroadcast(...)
builder.addAction(R.drawable.ic_check, "標記為已讀", actionIntent)

進度條 (Progress Bar)

適用於檔案下載或上傳任務。

val PROGRESS_MAX = 100
val PROGRESS_CURRENT = 45
// 顯示確定進度
builder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false)
// 顯示不確定進度 (跑馬燈)
builder.setProgress(0, 0, true)

管理與群組化

更新與刪除通知

  • 更新:使用同一個 notificationId 再次呼叫 notify()
  • 刪除:呼叫 manager.cancel(id)cancelAll()

通知的群組 (Grouping)

當發送多條相似通知(如多則聯絡人訊息)時,應使用 setGroup() 將其打包,避免霸佔使用者的通知空間。

val GROUP_KEY_MESSAGES = "com.android.example.MESSAGES"

val newMessage = NotificationCompat.Builder(context, channelId)
    .setGroup(GROUP_KEY_MESSAGES)
    // ...

透過精確的頻道劃分與豐富的樣式展示,通知將成為你 App 最重要的觸及工具。務必遵循規範,避免發送過多無關資訊干擾用戶。