关于 PendingIntent 您需要知道的那些事
PendingIntent
https://developer.android.google.cn/reference/android/app/PendingIntent重要更新
https://developer.android.google.cn/about/versions/12/behavior-changes-12#pending-intent-mutability
PendingIntent 是什么?
PendingIntent 对象封装了 Intent 对象的功能,同时以您应用的名义指定其他应用允许哪些操作的执行,来响应用户未来会进行的操作。比如,所封装的 Intent 可能会在闹铃关闭后或者用户点击通知时被触发。
PendingIntent 的关键点是其他应用在触发 intent 时是以您应用的名义。换而言之,其他应用会使用您应用的身份来触发 intent。
为了让 PendingIntent 具备和普通 Intent 一样的功能,系统会使用创建 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
)
FLAG_IMMUTABLE
https://developer.android.google.cn/reference/kotlin/android/app/PendingIntent#FLAG_IMMUTABLE:kotlin.IntNotificationManagerCompat.notify()
https://developer.android.google.cn/reference/androidx/core/app/NotificationManagerCompat#notify(java.lang.String,%20int,%20android.app.Notification)PendingIntent.send()
https://developer.android.google.cn/reference/kotlin/android/app/PendingIntent#send
更新不可变的 PendingIntent
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 已被更新
FLAG_UPDATE_CURRENT
https://developer.android.google.cn/reference/kotlin/android/app/PendingIntent#flag_update_current
跨应用 API
startActivityForResult()
https://developer.android.google.cn/reference/android/app/Activity#startActivityForResult(android.content.Intent,%20int,%20android.os.Bundle)onActivityResult()
https://developer.android.google.cn/reference/android/app/Activity#onActivityResult(int,%20int,%20android.content.Intent)接收回调
https://developer.android.google.cn/training/basics/intents/result
在本例中,订购应用使用了 PendingIntent 而没有直接发送 activity 结果,因为订单可能需要更长时间进行提交,而让用户在这个过程中等待是不合理的。
可变 PendingIntent
fun PendingIntent.send(
context: Context!,
code: Int,
intent: Intent?
)
版本 https://developer.android.google.cn/reference/kotlin/android/app/PendingIntent#send(android.content.Context,%20kotlin.Int,%20android.content.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
)
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.setComponent()
https://developer.android.google.cn/reference/kotlin/android/content/Intent.html#setComponent(android.content.ComponentName)
关于标记的详情
我们上面介绍了少数几个可用于创建 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,始终设置其中所封装的 Intent 的 ComponentName。如果未采取该操作的话可能会造成安全隐患。
该标记是在 Android 12 版本中加入。Android 12 之前的版本中,任何未指定 FLAG_IMMUTABLE 标记所创建的 PendingIntent 都是隐式可变类型。
ComponentName
https://developer.android.google.cn/reference/android/content/ComponentNameAndroid 12
https://developer.android.google.cn/reference/android/app/PendingIntent#FLAG_MUTABLE
FLAG_UPDATE_CURRENT: 向系统发起请求,使用新的 extra 数据更新已有的 PendingIntent,而不是保存新的 PendingIntent。如果 PendingIntent 未注册,则进行注册。
https://developer.android.google.cn/reference/kotlin/android/app/PendingIntent#flag_update_current
FLAG_ONE_SHOT: 仅允许 PendingIntent (通过 PendingIntent.send()) 被发送一次。对于传递 PendingIntent 时,其内部的 Intent 仅能被发送一次的场景就非常重要了。该机制可能便于操作,或者可以避免应用多次执行某项操作。
https://developer.android.google.cn/reference/kotlin/android/app/PendingIntent#flag_one_shot
🔐 使用 FLAG_ONE_SHOT 来避免类似 "重放攻击" 的问题。
重放攻击
https://en.wikipedia.org/wiki/Replay_attack
FLAG_CANCEL_CURRENT: 在注册新的 PendingIntent 之前,取消已存在的某个 PendingIntent。该标记用于当某个 PendingIntent 被发送到某应用,然后您希望将它转发到另一个应用,并更新其中的数据。使用 FLAG_CANCEL_CURRENT 之后,之前的应用将无法再调用 send 方法,而之后的应用可以调用。
https://developer.android.google.cn/reference/kotlin/android/app/PendingIntent#flag_cancel_current
接收 PendingIntent
有些情况下系统或者其他框架会将 PendingIntent 作为 API 调用的返回值。举一个典型例子是方法 MediaStore.createWriteRequest(),它是在 Android 11 中新增的。
static fun MediaStore.createWriteRequest(
resolver: ContentResolver,
uris: MutableCollection<Uri>
): PendingIntent
正如我们应用创建的 PendingIntent 一样,它是以我们应用的身份运行,而系统创建的 PendingIntent,它是以系统的身份运行。具体到这里 API 的使用场景,它允许应用打开 Activity 并赋予我们的应用 Uri 集合的写权限。
总结
ComponentName
https://developer.android.google.cn/reference/kotlin/android/content/ComponentName使用 Android 12 开发者预览版
https://developer.android.google.cn/about/versions/12/get告诉我们
https://developer.android.google.cn/about/versions/12/feedback
推荐阅读