关于 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 开发者预览版 测试您的应用,并 告诉咱们 您的使用体验。

相关文章
相关标签/搜索