PendingIntent是Android框架的重要组成部分。Android 12创建的每个PendingIntent对象必须使用PendingIntent.FLAG_MUTABLE或PendingIntent.FLAG_IMMUTABLE标志指定可变性,以提高应用的安全性。
PendingIntent对象包装了Intent对象的功能,同时允许指定另一个应用程序代替自己执行后续的操作。
PendingIntent的主要功能是其包装的Intent被另一个应用程序代替调用执行。也就是说,另一个应用程序以发送此Intent的应用程序的身份执行此Intent。
为了使PendingIntent具有与普通Intent相同的行为,系统根据创建时使用的相同标识来触发PendingIntent。
使用PendingIntent的最常见、最基本的方法是与通知关联操作:
Intent intent = Intent(applicationContext, MainActivity.class);
intent.setAction(NOTIFICATION_ACTION);
intent.setData(deepLink);
PendingIntent pendingIntent = PendingIntent.getActivity(applicationContext, NOTIFICATION_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE);
Notification notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL).setContentIntent(pendingIntent).build();
notificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
上面代码构建一个标准的Intent来打开应用程序,然后在将它添加到通知之前简单地将它包装在PendingIntent中。
在这种情况下,由于动作是确定的不可变的,因此使用FLAG_IMMUTABLE标志构造一个 PendingIntent,也就是说接收此PendingIntent的应用程序不能对其进行修改。
在调用NotificationManagerCompat.notify()之后,系统将显示通知,并且当用户点击该通知时,会调用PendingIntent.send()方法启动应用程序。
并不是应用程序需要更新PendingIntent那么它就需要是可变的。创建PendingIntent的应用程序始终可以通过传递标志FLAG_UPDATE_CURRENT来更新它:
Intent updatedIntent = Intent(applicationContext, MainActivity.class);
updatedIntent.setAction(NOTIFICATION_ACTION);
updatedIntent.setData(differentDeepLink);
PendingIntent updatedPendingIntent = PendingIntent.getActivity(applicationContext, NOTIFICATION_REQUEST_CODE, updatedIntent, PendingIntent.FLAG_IMMUTABLE|PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent不仅仅可以用来与系统进行交互,其他地方也可以用。使用startActivityForResult()启动一个活动并且在onActivityResult()中接收回调并不是唯一的方法。
假设有一个在线订购应用程序,它提供了API允许其他应用程序与其通信。它可能会接受PendingIntent作为它自己的Intent的extra参数,用于开始订购食物的过程。订单应用程序只有在订单交付后才会启动PendingIntent。
在这种情况下,订购应用程序使用PendingIntent而不是发送activity结果,因为交付订单可能需要大量时间,并且在这种情况发生时强制让用户等待是没有意义的。
因为不希望在线订购应用程序更改Intent的任何内容,只希望在订单到达时按原样发送,因此需要创建一个不可变的PendingIntent对象。
但是,如果订购应用程序的开发人员想要添加一项功能,允许用户输入一条消息,该消息将被发送回调用它的应用程序,这时候就要使用可变的PendingIntent。
由于PendingIntent本质上是Intent的包装,所以你可能会认为有一个PendingIntent.getIntent()的方法,可以调用它来获取和更新包装的Intent,但事实并非如此。那么它是如何工作的呢?
除了PendingIntent上的send()方法不带任何参数外,还有一些其他版本:
public void send(Context context, int code, @Nullable Intent intent)
这个传入的Intent参数不会替换PendingIntent中包含的Intent,而是用于填充在创建PendingIntent时未提供的包装Intent中的参数。
Intent orderDeliveredIntent = Intent(applicationContext, OrderDeliveredActivity.class);
orderDeliveredIntent.setAction(ACTION_ORDER_DELIVERED);
PendingIntent mutablePendingIntent = PendingIntent.getActivity(applicationContext, NOTIFICATION_REQUEST_CODE, orderDeliveredIntent, PendingIntent.FLAG_MUTABLE);
这个PendingIntent可以移交给在线订购应用程序。交付完成后,订单应用程序可以接收customerMessage并将其作为Intent extra发送回来,如下所示:
Intent intentWithExtrasToFill = new Intent();
intentWithExtrasToFill.putExtra(EXTRA_CUSTOMER_MESSAGE, customerMessage);
mutablePendingIntent.send(applicationContext, PENDING_INTENT_CODE, intentWithExtrasToFill);
然后,调用应用程序将在其Intent中看到额外的EXTRA_CUSTOMER_MESSAGE并能够显示消息。
创建可变PendingIntent时,始终明确设置将在Intent中启动的组件。这可以通过上面完成的方式完成,通过显式设置将接收它的组件类,但也可以通过调用Intent.setComponent()来完成。
应用程序调用Intent.setPackage()似乎更容易。如果这样做必须小心匹配多个组件的可能性。如果可能的话,最好指定特定的组件来接收Intent。
如果尝试覆盖使用FLAG_IMMUTABLE创建的PendingIntent中的值,则会失败,仍然会传递未修改的原始包装Intent对象。
应用程序始终可以更新自己的PendingIntent对象,即使它们是不可变的。使PendingIntent可变的唯一原因是另一个应用程序必须能够以某种方式更新包装的Intent对象。
已经讨论了一些在创建PendingIntent时可以使用的标志,但还有一些其他的标志。
FLAG_IMMUTABLE:表示PendingIntent中的Intent不能被将Intent传递给PendingIntent.send()的其他应用程序修改。应用总是可以使用FLAG_UPDATE_CURRENT来修改它自己的PendingIntent
在Android 12之前,默认情况下,不带此标志创建的PendingIntent是可变的。
在Android 6 (API 23) 之前的Android版本上,PendingIntent总是可变的。
FLAG_MUTABLE:表示PendingIntent中的Intent应允许应用程序通过合并PendingIntent.send()的Intent参数值来更新其内容。
始终填写任何可变的PendingIntent的包装Intent的ComponentName。不这样做可能会导致安全漏洞!
此标志是在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可以防止诸如“重放攻击”(replay attacks)之类的问题。
FLAG_CANCEL_CURRENT:取消现有的PendingIntent(如果存在),然后再注册新的PendingIntent。如果某个特定的PendingIntent已经被发送到了一个应用程序,与此同时又想将它发送到另一个应用程序,而且还有可能会更新数据,则此标志很重要。通过使用FLAG_CANCEL_CURRENT,第一个应用程序将无法再调用其send方法,但第二个应用程序可以。
有时系统或其他框架会提供PendingIntent作为API调用的返回。例如在Android 11中添加的MediaStore.createWriteRequest()方法。
public static PendingIntent MediaStore.createWriteRequest(@NonNull ContentResolver resolver, @NonNull Collection<Uri> uris)
正如应用程序创建的PendingIntent使用应用程序身份运行一样,系统创建的PendingIntent也是使用系统身份运行。就此API而言,其允许应用程序启动一个 Activity,该Activity可以向应用程序授予对Uris集合的写入权限。
public static void startActivityForService(Context context, MediaInfo mediaInfo) {
Intent intent = new Intent(context, ScreenshotResultActivity.class);
intent.putExtra(EXTRA_MEDIA_INFO, mediaInfo);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
try {
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
pendingIntent.send();
} catch (Exception ex) {
ex.printStackTrace();
context.startActivity(intent);
}
}
感谢大家的支持,如有错误请指正,如需转载请标明原文出处!