[11月的好奇心] PendingIntent 是啥?为什么很多场景不能直接用 Intent 非要用 PendingIntent?

Nov 08, 2018

出于对 PendingIntent 的好奇心,翻阅了很多资料。最后发现还是官方文档描述地到位。前三段话读下去已经解决了我的大部分疑惑。整篇读完有种豁然开朗的快感。
等翻译完,开始有点不解自己最初竟然会有为什么不直接使用 Intent 代替 PendingIntent? 这样的问题。

总之,对于对 PendingIntent 尚有疑问的同学来说,这文档是真不错。


/**
 * A description of an Intent and target action to perform with it.  Instances
 * of this class are created with {@link #getActivity}, {@link #getActivities},
 * {@link #getBroadcast}, and {@link #getService}; the returned object can be
 * handed to other applications so that they can perform the action you
 * described on your behalf at a later time.

这里说PendingIntentIntent行为的描述类。类似于我们用一个文件句柄,来说明它所对应的文件。
它由这四个方法创建:getActivity getActivities getBroadcast getService
它可以被其它应用调用以执行你预先声明的行为。

这里可以这么对比理解:
FileDescriptor 用来描述文件
Socket 用来描述网络
PendingIntent 用来描述行为

如果把类名改为 IntentDescriptor 或许会更好理解。


 *
 * <p>By giving a PendingIntent to another application,
 * you are granting it the right to perform the operation you have specified
 * as if the other application was yourself (with the same permissions and
 * identity).  As such, you should be careful about how you build the PendingIntent:
 * almost always, for example, the base Intent you supply should have the component
 * name explicitly set to one of your own components, to ensure it is ultimately
 * sent there and nowhere else.
 *

PendingIntent 交给其它应用,你就授予了它们进行其所定义的行为的权利。
所以你创建 PendingIntent 一定要格外小心:记住在你定义的 Intent 中加上限制用的包名、类名,以确保它只被你希望的对象使用。

这段是在说,我不仅告诉你你该做啥,连权限也给你了。

这搁在军营中,就是一道虎符啊。所以一定要声明好把虎符具体交到谁的手中,不然会很危险。


* <p>A PendingIntent itself is simply a reference to a token maintained by
* the system describing the original data used to retrieve it.  This means
* that, even if its owning application's process is killed, the
* PendingIntent itself will remain usable from other processes that
* have been given it.  If the creating application later re-retrieves the
* same kind of PendingIntent (same operation, same Intent action, data,
* categories, and components, and same flags), it will receive a PendingIntent
* representing the same token if that is still valid, and can thus call
* {@link #cancel} to remove it.
*

我们现在知道 PendingIntent 其实就是个 IntentDescriptor 行为描述符,但是它存到哪里了呢? maintained by the system,和文件一样,被系统维护。所以无论创建它的进程是否存在,它都和文件一样,被系统保存在了某个地方。只要你拥有“读取权限”,就能随时获取它。
如果创建 PendingIntent 的程序后来又要获取相同类型的 PendingIntent(相同的操作、相同的 Intent action、data、categories、component、flags),那么它会获取到和之前相同的 token, 前提是它仍然有效。此时你可以取消它,如果你希望的话。


* <p>Because of this behavior, it is important to know when two Intents
* are considered to be the same for purposes of retrieving a PendingIntent.
* A common mistake people make is to create multiple PendingIntent objects
* with Intents that only vary in their "extra" contents, expecting to get
* a different PendingIntent each time.  This does <em>not</em> happen.  The
* parts of the Intent that are used for matching are the same ones defined
* by {@link Intent#filterEquals(Intent) Intent.filterEquals}.  If you use two
* Intent objects that are equivalent as per
* {@link Intent#filterEquals(Intent) Intent.filterEquals}, then you will get
* the same PendingIntent for both of them.
*

这段说,上面说过使用两个相同的 Intent 会获取到同一个 PendingIntent,那么什么样的 Intent 被认为是相同的呢?
很多人常犯的错误是,认为只改变 Intent#extra 内容就可以获取到不同的 PendingIntent
而事实并非如此。
Intent 是否相同通过 Intent.filterEquals 判断。见下图。只有这个方法返回 false, 才能得到不同的 PendingIntent

filterEquals


* <p>There are two typical ways to deal with this.
*
* <p>If you truly need multiple distinct PendingIntent objects active at
* the same time (such as to use as two notifications that are both shown
* at the same time), then you will need to ensure there is something that
* is different about them to associate them with different PendingIntents.
* This may be any of the Intent attributes considered by
* {@link Intent#filterEquals(Intent) Intent.filterEquals}, or different
* request code integers supplied to {@link #getActivity}, {@link #getActivities},
* {@link #getBroadcast}, or {@link #getService}.
*
* <p>If you only need one PendingIntent active at a time for any of the
* Intents you will use, then you can alternatively use the flags
* {@link #FLAG_CANCEL_CURRENT} or {@link #FLAG_UPDATE_CURRENT} to either
* cancel or modify whatever current PendingIntent is associated with the
* Intent you are supplying.
*/

这段就比较啰嗦了,简单两句话说明

在某些场景有两种方法来避免获取到相同 PendingIntent

  1. 改变 Intent#filterEquals 中作为判断一句的某个字段
  2. 获取 PendingIntent 时使用不同的 requestCode

如果你有多个不同的 Intent,但你只需要一个活动的 PendingIntent。那么你需要通过和它绑定的 Intent 来更新或移除它。


上面介绍了这么多 PendingIntent,现在来正面谈一下标题内容,为什么不能用 Intent 代替 PendingIntent 呢?

Intent 定义了行为内容,我们知道很多操作会使用 Intent,比如 startActivitystartService 等,那究竟要用哪一个方法来调用 Intent 声明的内容呢?这就不是 Intent 所能决定的了。这是不能使用 Intent 代替 PendingIntent 的一个非常重要的因素。

/* 看板娘 */