乐趣区

关于android:关于-PendingIntent-您需要知道的那些事

PendingIntent 是 Android 框架中十分重要的组成部分,然而目前大多数与该主题相干的开发者资源更关注它的实现细节,即 “PendingIntent 是由系统维护的 token 援用 ”,而疏忽了它的用处。

因为 Android 12 对 PendingIntent 进行了 重要更新,包含须要显式确定 PendingIntent 是否是可变的,所以我认为有必要和大家深刻聊聊 PendingIntent 有什么作用,零碎如何应用它,以及为什么您会须要可变类型的 PendingIntent。

PendingIntent 是什么?

PendingIntent 对象封装了 Intent 对象的性能,同时以您利用的名义指定其余利用容许哪些操作的执行,来响应用户将来会进行的操作。比方,所封装的 Intent 可能会在闹铃敞开后或者用户点击告诉时被触发。

PendingIntent 的关键点是其余利用在触发 intent 时是 以您利用的名义。换而言之,其余利用会应用您利用的身份来触发 intent。

为了让 PendingIntent 具备和一般 Intent 一样的性能,零碎会应用创立 PendingIntent 时的身份来触发它。在大多数状况下,比方闹铃和告诉,其中所用到的身份就是利用自身。

咱们来看利用中应用 PendingIntent 的不同形式,以及咱们应用这些形式的起因。

惯例用法

应用 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 标记构建了无奈被批改的 PendingIntent

调用 NotificationManagerCompat.notify()) 之后工作就实现了。当零碎显示告诉,且用户点击告诉时,会在咱们的 PendingIntent 上调用 PendingIntent.send(),来启动咱们的利用。

更新不可变的 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 开发者预览版 测试您的利用,并 通知咱们 您的应用体验。

退出移动版