關於 PendingIntent 您須要知道的那些事

PendingIntent 是 Android 框架中很是重要的組成部分,可是目前大多數與該主題相關的開發者資源更關注它的實現細節,即 "PendingIntent 是由系統維護的 token 引用",而忽略了它的用途。html

因爲 Android 12 對 PendingIntent 進行了 重要更新,包括須要顯式肯定 PendingIntent 是不是可變的,因此我認爲有必要和你們深刻聊聊 PendingIntent 有什麼做用,系統如何使用它,以及爲何您會須要可變類型的 PendingIntent。java

PendingIntent 是什麼?

PendingIntent 對象封裝了 Intent 對象的功能,同時以您應用的名義指定其餘應用容許哪些操做的執行,來響應用戶將來會進行的操做。好比,所封裝的 Intent 可能會在鬧鈴關閉後或者用戶點擊通知時被觸發。android

PendingIntent 的關鍵點是其餘應用在觸發 intent 時是 以您應用的名義。換而言之,其餘應用會使用您應用的身份來觸發 intent。segmentfault

爲了讓 PendingIntent 具有和普通 Intent 同樣的功能,系統會使用建立 PendingIntent 時的身份來觸發它。在大多數狀況下,好比鬧鈴和通知,其中所用到的身份就是應用自己。安全

咱們來看應用中使用 PendingIntent 的不一樣方式,以及咱們使用這些方式的緣由。app

常規用法

使用 PendingIntent 最常規最基礎的用法是做爲關聯某個通知所進行的操做。框架

val intent = Intent(applicationContext, MainActivity::class.java).apply {
    action = NOTIFICATION_ACTION
    data = deepLink
}
val pendingIntent = PendingIntent.getActivity(
    applicationContext,
    NOTIFICATION_REQUEST_CODE,
    intent,
    PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(
        applicationContext,
        NOTIFICATION_CHANNEL
    ).apply {
        // ...
        setContentIntent(pendingIntent)
        // ...
    }.build()
notificationManager.notify(
    NOTIFICATION_TAG,
    NOTIFICATION_ID,
    notification
)

能夠看到咱們構建了一個標準類型的 Intent 來打開咱們的應用,而後,在添加到通知以前簡單用 PendingIntent 封裝了一下。測試

在本例中,因爲咱們明確知道將來須要進行的操做,因此咱們使用 FLAG_IMMUTABLE 標記構建了沒法被修改的 PendingIntentui

調用 NotificationManagerCompat.notify()) 以後工做就完成了。當系統顯示通知,且用戶點擊通知時,會在咱們的 PendingIntent 上調用 PendingIntent.send(),來啓動咱們的應用。google

更新不可變的 PendingIntent

您也許會認爲若是應用須要更新 PendingIntent,那麼它須要是可變類型,但其實並非。應用所建立的 PendingIntent 可經過 FLAG_UPDATE_CURRENT 標記來更新。

val updatedIntent = Intent(applicationContext, MainActivity::class.java).apply {
   action = NOTIFICATION_ACTION
   data = differentDeepLink
}

// 因爲咱們使用了 FLAG_UPDATE_CURRENT 標記,因此這裏能夠更新咱們在上面建立的 
// PendingIntent
val updatedPendingIntent = PendingIntent.getActivity(
   applicationContext,
   NOTIFICATION_REQUEST_CODE,
   updatedIntent,
   PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
// 該 PendingIntent 已被更新

在接下來的內容中咱們會解釋爲何將 PendingIntent 設置爲可變類型。

跨應用 API

一般的用法並不侷限於與系統交互。雖然在某些操做後使用 startActivityForResult()) 和 onActivityResult()) 來 接收回調 是很是常見的用法,但它並非惟一用法。

想象一下一個線上訂購應用提供了 API 使其餘應用能夠集成。當 Intent 啓動了訂購食物的流程後,應用能夠 Intentextra 的方式訪問 PendingIntent。一旦訂單完成傳遞,訂購應用僅需啓動一次 PendingIntent

在本例中,訂購應用使用了 PendingIntent 而沒有直接發送 activity 結果,由於訂單可能須要更長時間進行提交,而讓用戶在這個過程當中等待是不合理的。

咱們但願建立一個不可變的 PendingIntent,由於咱們不但願線上訂購應用修改咱們的 Intent。當訂單生效時,咱們僅但願其餘應用發送它,並保持它自己不變。

可變 PendingIntent

可是若是咱們做爲訂購應用的開發者,但願添加一個特性能夠容許用戶回送消息至調用訂購功能的應用呢?好比可讓調用的應用提示,"如今是披薩時間!"

要實現這樣的效果就須要使用可變的 PendingIntent 了。

既然 PendingIntent 本質上是 Intent 的封裝,有人可能會想能夠經過一個 PendingIntent.getIntent() 方法來得到其中所封裝的 Intent。可是答案是不能夠的。那麼該如何實現呢?

PendingIntent 中除了不含任何參數的 send() 方法以外,還有其餘 send 方法的版本,包括這個能夠接受 Intent 做爲參數的 版本):

fun PendingIntent.send(
   context: Context!,
   code: Int,
   intent: Intent?
)

這裏的 Intent 參數並不會替換 PendingIntent 所封裝的 Intent,而是經過 PendingIntent 在建立時所封裝的 Intent 來填充參數。

咱們來看下面的例子。

val orderDeliveredIntent = Intent(applicationContext, OrderDeliveredActivity::class.java).apply {
   action = ACTION_ORDER_DELIVERED
}
val mutablePendingIntent = PendingIntent.getActivity(
   applicationContext,
   NOTIFICATION_REQUEST_CODE,
   orderDeliveredIntent,
   PendingIntent.FLAG_MUTABLE
)

這裏的 PendingIntent 會被傳遞到咱們的線上訂購應用。當傳遞完成後,應用能夠獲得一個 customerMessage,並將其做爲 intent 的 extra 回傳,以下示例所示:

val intentWithExtrasToFill = Intent().apply {
  putExtra(EXTRA_CUSTOMER_MESSAGE, customerMessage)
}
mutablePendingIntent.send(
  applicationContext,
  PENDING_INTENT_CODE,
  intentWithExtrasToFill
)

調用端的應用會在它的 Intent 中獲得 EXTRA_CUSTOMER_MESSAGE extra,並顯示消息。

聲明可變的 PendingIntent 時須要特別注意的事

⚠️當建立可變的 PendingIntent 時,始終 顯式設置要啓動的 Intent 的 component。能夠經過咱們上面的實現方式操做,即顯式設置要接收的準確類名,不過也能夠經過 Intent.setComponent()) 實現。

您的應用可能會在某些場景下調用 Intent.setPackage() 來實現更方便。可是請特別注意這樣的作法有可能會 匹配到多個 component。若是能夠的話,最好指定特定的 component。

⚠️若是您嘗試覆寫使用 FLAG_IMMUTABLE 建立的 PendingIntent 中的值,那麼該操做會失敗且沒有任何提示,並傳遞原始封裝未修改的 Intent

請記住應用老是能夠更新自身的 PendingIntent,即便是不可變類型。使 PendingIntent 成爲可變類型的惟一緣由是其餘應用須要經過某種方式更新其中封裝的 Intent

關於標記的詳情

咱們上面介紹了少數幾個可用於建立 PendingIntent 的標記,還有一些標記也爲你們介紹一下。

FLAG_IMMUTABLE: 表示其餘應用經過 PendingIntent.send() 發送到 PendingIntent 中的 Intent 沒法被修改。應用老是可使用 FLAG_UPDATE_CURRENT 標記來修改它本身的 PendingIntent。

在 Android 12 以前的系統中,不帶有該標記建立的 PendingIntent 默認是可變類型。

⚠️ Android 6 (API 23) 以前的系統中,PendingIntent 都是可變類型。

🆕FLAG_MUTABLE: 表示由 PendingIntent.send() 傳入的 intent 內容能夠被應用合併到 PendingIntent 中的 Intent。

⚠️ 對於任何可變類型的 PendingIntent,始終 設置其中所封裝的 IntentComponentName。若是未採起該操做的話可能會形成安全隱患。

該標記是在 Android 12 版本中加入。Android 12 以前的版本中,任何未指定 FLAG_IMMUTABLE標記所建立的 PendingIntent 都是隱式可變類型。

FLAG_UPDATE_CURRENT: 向系統發起請求,使用新的 extra 數據更新已有的 PendingIntent,而不是保存新的 PendingIntent。若是 PendingIntent 未註冊,則進行註冊。

FLAG_ONE_SHOT: 僅容許 PendingIntent (經過 PendingIntent.send()) 被髮送一次。對於傳遞 PendingIntent 時,其內部的 Intent 僅能被髮送一次的場景就很是重要了。該機制可能便於操做,或者能夠避免應用屢次執行某項操做。

🔐 使用 FLAG_ONE_SHOT 來避免相似 "重放攻擊" 的問題。

FLAG_CANCEL_CURRENT: 在註冊新的 PendingIntent 以前,取消已存在的某個 PendingIntent。該標記用於當某個 PendingIntent 被髮送到某應用,而後您但願將它轉發到另外一個應用,並更新其中的數據。使用 FLAG_CANCEL_CURRENT 以後,以前的應用將沒法再調用 send 方法,而以後的應用能夠調用。

接收 PendingIntent

有些狀況下系統或者其餘框架會將 PendingIntent 做爲 API 調用的返回值。舉一個典型例子是方法 MediaStore.createWriteRequest(),它是在 Android 11 中新增的。

static fun MediaStore.createWriteRequest(
   resolver: ContentResolver,
   uris: MutableCollection<Uri>
): PendingIntent

正如咱們應用建立的 PendingIntent 同樣,它是以咱們應用的身份運行,而系統建立的 PendingIntent,它是以系統的身份運行。具體到這裏 API 的使用場景,它容許應用打開 Activity 並賦予咱們的應用 Uri 集合的寫權限。

總結

咱們在本文中介紹了 PendingIntent 如何做爲 Intent 的封裝使系統或者其餘應用可以在將來某一時間以某個應用的身份啓動該應用所建立的 Intent。

咱們還介紹了 PendingIntent 爲什麼須要設置爲不可變,以及這麼作並不會影響應用修改自身所建立的 PendingIntent 對象。能夠經過 FLAG_UPDATE_CURRENT 標記加上 FLAG_IMMUTABLE 來實現該操做。

咱們還介紹了若是 PendingIntent 是可變的,須要作的預防措施 — 保證對封裝的 Intent 設置 ComponentName

最後,咱們介紹了有時系統或者框架如何嚮應用提供 PendingIntent,以便咱們可以決定如何而且什麼時候運行它們。

Android 12 中提高了應用的安全性,PendingIntent 的這些更新與之相得益彰。更多內容請查閱咱們以前的推文《Android 12 首個開發者預覽版到來》。

如需瞭解更多,歡迎 使用 Android 12 開發者預覽版 測試您的應用,並 告訴咱們 您的使用體驗。

相關文章
相關標籤/搜索