返回介绍

说说 PendingIntent 的内部机制

发布于 2025-02-28 12:35:45 字数 20356 浏览 0 评论 0 收藏 0

http://www.tuicool.com/articles/22iMZj

说说 PendingIntent 的内部机制

侯 亮

1 概述

在 Android 中,我们常常使用 PendingIntent 来表达一种“留待日后处理”的意思。从这个角度来说,PendingIntent 可以被理解为一种特殊的异步处理机制。不过,单就命名而言,PendingIntent 其实具有一定误导性,因为它既不继承于 Intent,也不包含 Intent,它的核心可以粗略地汇总成四个字——“异步激发”。

很明显,这种异步激发常常是要跨进程执行的。比如说 A 进程作为发起端,它可以从系统“获取”一个 PendingIntent,然后 A 进程可以将 PendingIntent 对象通过 binder 机制“传递”给 B 进程,再由 B 进程在未来某个合适时机,“回调”PendingIntent 对象的 send() 动作,完成激发。

在 Android 系统中,最适合做集中性管理的组件就是 AMS(Activity Manager Service)啦,所以它义不容辞地承担起管理所有 PendingIntent 的职责。这样我们就可以画出如下示意图:

注意其中的第 4 步“递送相应的 intent”。这一步递送的 intent 是从何而来的呢?简单地说,当发起端获取 PendingIntent 时,其实是需要同时提供若干 intent 的。这些 intent 和 PendingIntent 只是配套的关系,而不是聚合的关系,它们会被缓存在 AMS 中。日后,一旦处理端将 PendingIntent 的“激发”语义传递到 AMS,AMS 就会尝试找到与这个 PendingIntent 对应的若干 intent,并递送出去。

当然,以上说的只是大概情况,实际的技术细节会更复杂一点儿。下面我们就来谈谈细节。

2 PendingIntent 的技术细节

2.1 发起端获取 PendingIntent

我们先要理解,所谓的“发起端获取 PendingIntent”到底指的是什么。难道只是简单 new 一个 PendingIntent 对象吗?当然不是。此处的“获取”动作其实还含有向 AMS“注册”intent 的语义。

在 PendingIntent.java 文件中,我们可以看到有如下几个比较常见的静态函数:

  • public static PendingIntent getActivity (Context context, int requestCode, Intent intent, int flags)
  • public static PendingIntent getBroadcast (Context context, int requestCode, Intent intent, int flags)
  • public static PendingIntent getService (Context context, int requestCode, Intent intent, int flags)
  • public static PendingIntent getActivities (Context context, int requestCode, Intent[] intents, int flags)
  • public static PendingIntent getActivities (Context context, int requestCode, Intent[] intents, int flags, Bundle options)

它们就是我们常用的获取 PendingIntent 的动作了。

坦白说,这几个函数的命名可真不怎么样,所以我们简单解释一下。上面的 getActivity() 的意思其实是,获取一个 PendingIntent 对象,而且该对象日后激发时所做的事情是启动一个新 activity。也就是说,当它异步激发时,会执行类似 Context.startActivity() 那样的动作。相应地,getBroadcast() 和 getService() 所获取的 PendingIntent 对象在激发时,会分别执行类似 Context..sendBroadcast() 和 Context.startService() 这样的动作。至于最后两个 getActivities(),用得比较少,激发时可以启动几个 activity。

我们以 getActivity() 的代码来说明问题:

public static PendingIntent getActivity(Context context, int requestCode,
                    Intent intent, int flags, Bundle options) 
{
  String packageName = context.getPackageName();
  String resolvedType = intent != null ? 
              intent.resolveTypeIfNeeded(context.getContentResolver()) : null;
  try 
  {
    intent.setAllowFds(false);
    IIntentSender target = ActivityManagerNative.getDefault().getIntentSender(
                  ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
                  null, null, requestCode, new Intent[] { intent },
                  resolvedType != null ? new String[] { resolvedType } : null,
                  flags, options);
    return target != null ? new PendingIntent(target) : null;
  } 
  catch (RemoteException e) 
  {}
  return null;
}

其中那句 new PendingIntent(target) 创建了 PendingIntent 对象,其重要性自不待言。然而,这个对象的内部核心其实是由上面那个 getIntentSender() 函数得来的。而这个 IIntentSender 核心才是我们真正需要关心的东西。

说穿了,此处的 IIntentSender 对象是个 binder 代理,它对应的 binder 实体是 AMS 中的 PendingIntentRecord 对象。PendingIntent 对象构造之时,IIntentSender 代理作为参数传进来,并记录在 PendingIntent 的 mTarget 域。日后,当 PendingIntent 执行异步激发时,其内部就是靠这个 mTarget 域向 AMS 传递语义的。

我们前文说过,PendingIntent 常常会经由 binder 机制,传递到另一个进程去。而 binder 机制可以保证,目标进程得到的 PendingIntent 的 mTarget 域也是合法的 IIntentSender 代理,而且和发起端的 IIntentSender 代理对应着同一个 PendingIntentRecord 实体。示意图如下:

2.2 AMS 里的 PendingIntentRecord

那么 PendingIntentRecord 里又有什么信息呢?它的定义截选如下:

class PendingIntentRecord extends IIntentSender.Stub 
{
  final ActivityManagerService owner;
  final Key key;  // 最关键的 key 域
  final int uid;
  final WeakReference<PendingIntentRecord> ref;
  boolean sent = false;
  boolean canceled = false;
  String stringName;
  . . . . . .
}

请注意其中那个 key 域。这里的 Key 是个 PendingIntentRecord 的内嵌类,其定义截选如下:

final static class Key 
{
  final int type;
  final String packageName;
  final ActivityRecord activity;
  final String who;
  final int requestCode;
  final Intent requestIntent;    // 注意!
  final String requestResolvedType;
  final Bundle options;
  Intent[] allIntents;  // 注意!记录了当初获取 PendingIntent 时,用户所指定的所有 intent
  String[] allResolvedTypes;
  final int flags;
  final int hashCode;
  . . . . . .
  . . . . . .
}

请注意其中的 allIntents[]数组域以及 requestIntent 域。前者记录了当初获取 PendingIntent 时,用户所指定的所有 intent(虽然一般情况下只会指定一个 intent,但类似 getActivities() 这样的函数还是可以指定多个 intent 的),而后者可以粗浅地理解为用户所指定的那个 intent 数组中的最后一个 intent。现在大家应该清楚异步激发时用到的 intent 都存在哪里了吧。

Key 的构造函数截选如下:

Key(int _t, String _p, ActivityRecord _a, String _w,
  int _r, Intent[] _i, String[] _it, int _f, Bundle _o) 
{
  type = _t;
  packageName = _p;
  activity = _a;
  who = _w;
  requestCode = _r;
  requestIntent = _i != null ? _i[_i.length-1] : null;  // intent 数组中的最后一个
  requestResolvedType = _it != null ? _it[_it.length-1] : null;
  allIntents = _i;  // 所有 intent
  allResolvedTypes = _it;
  flags = _f;
  options = _o;
  . . . . . .
}

Key 不光承担着记录信息的作用,它还承担“键值”的作用。

2.3 AMS 中的 PendingIntentRecord 总表

在 AMS 中,管理着系统中所有的 PendingIntentRecord 节点,所以需要把这些节点组织成一张表:

final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> 
                             mIntentSenderRecords

这张哈希映射表的键值类型就是刚才所说的 PendingIntentRecord.Key。

以后每当我们要获取 PendingIntent 对象时,PendingIntent 里的 mTarget 是这样得到的:AMS 会先查 mIntentSenderRecords 表,如果能找到符合的 PendingIntentRecord 节点,则返回之。如果找不到,就创建一个新的 PendingIntentRecord 节点。因为 PendingIntentRecord 是个 binder 实体,所以经过 binder 机制传递后,客户进程拿到的就是个合法的 binder 代理。如此一来,前文的示意图可以进一步修改成下图:

2.4 AMS 里的 getIntentSender() 函数

现在,我们回过头继续说前文的 getActivity(),以及其调用的 getIntentSender()。我们先列一遍 getActivity() 的原型:

public static PendingIntent getActivity(Context context, int requestCode,
                    Intent intent, int flags, Bundle options)
  • context 参数是调用方的上下文。
  • requestCode 是个简单的整数,起区分作用。
  • intent 是异步激发时将发出的 intent。
  • flags 可以包含一些既有的标识,比如 FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT 等等。不少同学对这个域不是很清楚,我们后文会细说。
  • options 可以携带一些额外的数据。

getActivity() 的代码很简单,其参数基本上都传给了 getIntentSender()。

IIntentSender target = ActivityManagerNative.getDefault().getIntentSender(. . . . . .)

getIntentSender() 的原型大体是这样的:

public IIntentSender getIntentSender(int type,
      String packageName, IBinder token, String resultWho,
      int requestCode, Intent[] intents, String[] resolvedTypes,
      int flags, Bundle options) throws RemoteException;

其参数比 getActivity() 要多一些,我们逐个说明。

type 参数表明 PendingIntent 的类型。getActivity() 和 getActivities() 动作里指定的类型值是 INTENT_SENDER_ACTIVITY,getBroadcast() 和 getService() 和动作里指定的类型值分别是 INTENT_SENDER_BROADCAST 和 INTENT_SENDER_SERVICE。另外,在 Activity.java 文件中,我们还看到一个 createPendingResult() 函数,这个函数表达了发起方的 activity 日后希望得到 result 回馈的意思,所以其内部调用 getIntentSender() 时指定的类型值为 INTENT_SENDER_ACTIVITY_RESULT。

packageName 参数表示发起端所属的包名。

token 参数是个指代回馈目标方的代理。这是什么意思呢?我们常用的 getActivity()、getBroadcast() 和 getService() 中,只是把这个参数简单地指定为 null,表示这个 PendingIntent 激发时,是不需要发回什么回馈的。不过当我们希望获取类型为 INTENT_SENDER_ACTIVITY_RESULT 的 PendingIntent 时,就需要指定 token 参数了。具体可参考 createPendingResult() 的代码:

public PendingIntent createPendingResult(int requestCode, Intent data, int flags) 
{
  String packageName = getPackageName();
  try 
  {
    data.setAllowFds(false);
    IIntentSender target = ActivityManagerNative.getDefault().getIntentSender(
                  ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, 
                  packageName,
                  mParent == null ? mToken : mParent.mToken,
                  mEmbeddedID, requestCode, new Intent[] { data }, 
                  null, flags, null);
    return target != null ? new PendingIntent(target) : null;
  } catch (RemoteException e) {
    // Empty
  }
  return null;
}

看到了吗?传入的 token 为 Activity 的 mToken 或者其 mParent.mToken。说得简单点儿,AMS 内部可以根据这个 token 找到其对应的 ActivityRecord,日后当 PendingIntent 激发时,AMS 可以根据这个 ActivityRecord 确定出该向哪个目标进程的哪个 Activity 发出 result 语义。

resultWho 参数和 token 参数息息相关,一般也是 null 啦。在 createPendingResult() 中,其值为 Activity 的 mEmbeddedID 字符串。

requestCode 参数是个简单的整数,可以在获取 PendingIntent 时由用户指定,它可以起区分的作用。

intents 数组参数是异步激发时希望发出的 intent。对于 getActivity()、getBroadcast() 和 getService() 来说,都只会指定一个 intent 而已。只有 getActivities() 会尝试一次传入若干 intent。

resolvedTypes 参数基本上和 intent 是相关的。一般是这样得到的:

String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
        context.getContentResolver()) : null;

这个值常常和 intent 内部的 mData URI 有关系,比如最终的值可能是 URI 对应的 MIME 类型。

flags 参数可以指定 PendingIntent 的一些行为特点。它的取值是一些既有的比特标识的组合。目前可用的标识有:FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT 等等。有时候,flags 中还可以附带若干 FILL_IN_XXX 标识。我们把常见的标识定义列举如下:

【PendingIntent 中】

public static final int FLAG_ONE_SHOT = 1<<30;
public static final int FLAG_NO_CREATE = 1<<29;
public static final int FLAG_CANCEL_CURRENT = 1<<28;
public static final int FLAG_UPDATE_CURRENT = 1<<27;

【Intent 中】

public static final int FILL_IN_ACTION = 1<<0;
public static final int FILL_IN_DATA = 1<<1;
public static final int FILL_IN_CATEGORIES = 1<<2;
public static final int FILL_IN_COMPONENT = 1<<3;
public static final int FILL_IN_PACKAGE = 1<<4;
public static final int FILL_IN_SOURCE_BOUNDS = 1<<5;
public static final int FILL_IN_SELECTOR = 1<<6;
public static final int FILL_IN_CLIP_DATA = 1<<7;

这些以 FILL_IN_打头的标志位,主要是在 intent 对象的 fillIn() 函数里起作用:

public int fillIn(Intent other, int flags)

我们以 FILL_IN_ACTION 为例来说明,当一个当我们执行类似 srcIntent.fillIn(otherIntent, ...) 的句子时,如果 otherIntent 的 mAction 域不是 null 值,那么 fillIn() 会在以下两种情况下,用 otherIntent 的 mAction 域值为 srcIntent 的 mAction 域赋值:

1) 当 srcIntent 的 mAction 域值为 null 时;
2) 如果 fillIn 的 flags 参数里携带了 FILL_IN_ACTION 标志位,那么即便 srcIntent 的 mAction 已经有值了,此时也会用 otherIntent 的 mAction 域值强行替换掉 srcIntent 的 mAction 域值。

其他 FILL_IN_标志位和 FILL_IN_ACTION 的处理方式类似,我们不再赘述。

options 参数可以携带一些额外数据。

2.4.1 getIntentSender() 函数

getIntentSender() 函数摘录如下:

public IIntentSender getIntentSender(int type, String packageName, 
                   IBinder token, String resultWho,
                   int requestCode, Intent[] intents, 
                   String[] resolvedTypes,
                   int flags, Bundle options) 
{
  . . . . . .  
  // 先判断 intents 数组,可以用伪代码 checkIntents(intents) 来表示
  // checkIntents(intents);
  . . . . . .  
  int callingUid = Binder.getCallingUid();
  . . . . . .
  if (callingUid != 0 && callingUid != Process.SYSTEM_UID) 
  {
    int uid = AppGlobals.getPackageManager().getPackageUid(packageName, 
                                 UserId.getUserId(callingUid));
    if (!UserId.isSameApp(callingUid, uid)) 
    {
      . . . . . .
      throw new SecurityException(msg);
    }
  }
  . . . . . .
  return getIntentSenderLocked(type, packageName, Binder.getOrigCallingUid(),
      token, resultWho, requestCode, intents, resolvedTypes, flags, options);
  . . . . . .
}

getIntentSender() 函数中有一段逐条判断 intents[]的代码,我用伪代码 checkIntents(intents) 来表示,这部分对应的实际代码如下:

for (int i=0; i<intents.length; i++) 
{
  Intent intent = intents[i];
  if (intent != null) 
  {
    if (intent.hasFileDescriptors()) 
    {
      throw new IllegalArgumentException("File descriptors passed in Intent");
    }
    if (type == ActivityManager.INTENT_SENDER_BROADCAST &&
      (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) 
    {
      throw new IllegalArgumentException("Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
    }
    intents[i] = new Intent(intent);
  }
}

这段代码说明在获取 PendingIntent 对象时,intent 中是不能携带文件描述符的。而且如果这个 PendingIntent 是那种要发出广播的 PendingIntent,那么 intent 中也不能携带 FLAG_RECEIVER_BOOT_UPGRADE 标识符。“BOOT_UPGRADE”应该是“启动并升级”的意思,它不能使用 PendingIntent。

getIntentSender() 中最核心的一句应该是调用 getIntentSenderLocked() 的那句。

2.4.2 getIntentSenderLocked() 函数

getIntentSenderLocked() 的代码截选如下:

【frameworks/base/services/java/com/android/server/am/ActivityManagerService.java】

IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, 
                  IBinder token, String resultWho,
                  int requestCode, Intent[] intents, 
                  String[] resolvedTypes, int flags,
                  Bundle options) 
{
  . . . . . .
  // 如果是 INTENT_SENDER_ACTIVITY_RESULT 类型,那么要判断 token 所
  // 代表的 activity 是否还在 activity 栈中

  . . . . . .
  // 整理 flags 中的信息

  . . . . . .
  PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, 
                                activity, resultWho,
                                requestCode, intents, 
                                resolvedTypes, flags, options);
  // 尽力从哈希映射表中查找 key 对应的 PendingIntentRecord,如果找不到就创建一个新的节点。
  WeakReference<PendingIntentRecord> ref;
  ref = mIntentSenderRecords.get(key);  
  PendingIntentRecord rec = ref != null ? ref.get() : null;
  if (rec != null) 
  {
    // 找到了匹配的 PendingIntent,现在考虑要不要更新它,或者取消它。
    if (!cancelCurrent) 
    {
      if (updateCurrent) 
      {
        // 如果明确指定了 FLAG_UPDATE_CURRENT,那么更新找到的节点
        if (rec.key.requestIntent != null) {
          rec.key.requestIntent.replaceExtras(intents != null ?
              intents[intents.length - 1] : null);
        }
        if (intents != null) {
          intents[intents.length-1] = rec.key.requestIntent;
          rec.key.allIntents = intents;
          rec.key.allResolvedTypes = resolvedTypes;
        } else {
          rec.key.allIntents = null;
          rec.key.allResolvedTypes = null;
        }
      }
      // 凡是能找到对应的节点,而且又不取消该节点的,那么就 return 这个节点
      return rec;
    }
    // 如果 PendingIntent 的标志中带有 FLAG_CANCEL_CURRENT,则从哈希映射表中删除之
    rec.canceled = true;
    mIntentSenderRecords.remove(key);
  }
  if (noCreate) 
  {
    // 如果明确表示了不创建新节点,也就是说标志中带有 FLAG_NO_CREATE,
    // 那么不管是不是 Cancel 了 PendingIntent,此时一概直接返回。
    return rec;
  }

  // 从哈希映射表中找不到,而且又没有写明 FLAG_NO_CREATE,此时创建一个新节点
  rec = new PendingIntentRecord(this, key, callingUid);
  mIntentSenderRecords.put(key, rec.ref);
  if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) 
  {
    // 如果 intent 需要返回结果,那么修改 token 对应的 ActivityRecord
    // 的 pendingResults 域。
    if (activity.pendingResults == null) 
    {
      activity.pendingResults = new HashSet<WeakReference<PendingIntentRecord>>();
    }
    activity.pendingResults.add(rec.ref);
  }
  return rec;
}

上面这段代码主要做的事情有:

1) 将传进来的多个参数信息整理成一个 PendingIntentRecord.Key 对象(key);
2) 尝试从 mIntentSenderRecords 总表中查找和 key 相符的 PendingIntentRecord 节点;
3) 根据 flags 参数所含有的意义,对得到的 PendingIntentRecord 进行加工。有时候修改之,有时候删除之。
4) 如果在总表中没有找到对应的 PendingIntentRecord 节点,或者根据 flags 的语义删除了刚找到的节点,那么此时的默认行为是创建一个新的 PendingIntentRecord 节点,并插入总表。除非 flags 中明确指定了 FLAG_NO_CREATE,此时不会创建新节点。

2.4.3 说说 flags

从 getIntentSenderLocked() 的代码中,我们终于搞明白了 flags 中那些特定比特值的意义了。我们现在总结一下。

应该说这些 flags 比特值基本上都是在围绕着 mIntentSenderRecords 总表说事的。其中,FLAG_CANCEL_CURRENT 的意思是,当我们获取 PendingIntent 时,如果可以从总表中查到一个相符的已存在的 PendingIntentRecord 节点的话,那么需要把这个节点从总表中清理出去。而在没有指定 FLAG_CANCEL_CURRENT 的大前提下,如果用户指定了 FLAG_UPDATE_CURRENT 标识,那么会用新的 intents 参数替掉刚查到的 PendingIntentRecord 中的旧 intents。

而不管是刚清理了已存在的 PendingIntentRecord,还是压根儿就没有找到符合的 PendingIntentRecord,只要用户没有明确指定 FLAG_NO_CREATE 标识,系统就会尽力创建一个新的 PendingIntentRecord 节点,并插入总表。

至于 FLAG_ONE_SHOT 标识嘛,它并没有在 getIntentSenderLocked() 中露脸儿。它的名字是“FLAG_ONE_SHOT”,也就是“只打一枪”的意思,那么很明显,这个标识起作用的地方应该是在“激发”函数里。在最终的激发函数(sendInner())里,我们可以看到下面的代码:

【frameworks/base/services/java/com/android/server/am/PendingIntentRecord.java】

int sendInner(int code, Intent intent, String resolvedType,
    IIntentReceiver finishedReceiver, String requiredPermission,
    IBinder resultTo, String resultWho, int requestCode,
    int flagsMask, int flagsValues, Bundle options) 
{
  synchronized(owner) {
    if (!canceled) 
    {
      sent = true;
      if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
        owner.cancelIntentSenderLocked(this, true);
        canceled = true;
      }
      . . . . . .
      . . . . . .
    }
  }
  return ActivityManager.START_CANCELED;
}

意思很简单,一进行激发就把相应的 PendingIntentRecord 节点从总表中清理出去,而且把 PendingIntentRecord 的 canceled 域设为 true。这样,以后即便外界再调用 send() 动作都没用了,因为再也无法进入 if (!canceled) 判断了。

2.4.4 将 PendingIntentRecord 节点插入总表

接下来 getIntentSenderLocked() 函数 new 了一个 PendingIntentRecord 节点,并将之插入 mIntentSenderRecords 总表中。

2.5 PendingIntent 的激发动作

下面我们来看 PendingIntent 的激发动作。在前文我们已经说过,当需要激发 PendingIntent 之时,主要是通过调用 PendingIntent 的 send() 函数来完成激发动作的。PendingIntent 提供了多个形式的 send() 函数,然而这些函数的内部其实调用的是同一个 send(),其函数原型如下:

public void send(Context context, int code, Intent intent,
          OnFinished onFinished, Handler handler, String requiredPermission) 
          throws CanceledException

该函数内部最关键的一句是:

int res = mTarget.send(code, intent, resolvedType,
          onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null,
          requiredPermission);

我们前文已经介绍过这个 mTarget 域了,它对应着 AMS 中的某个 PendingIntentRecord。

所以我们要看一下 PendingIntentRecord 一侧的 send() 函数,其代码如下:

public int send(int code, Intent intent, String resolvedType,
           IIntentReceiver finishedReceiver, String requiredPermission) 
{
  return sendInner(code, intent, resolvedType, finishedReceiver,
        requiredPermission, null, null, 0, 0, 0, null);
}

其中 sendInner() 才是真正做激发动作的函数。

sendInner() 完成的主要逻辑动作有:

1) 如果当前 PendingIntentRecord 节点已经处于 canceled 域为 true 的状态,那么说明这个节点已经被取消掉了,此时 sendInner() 不会做任何实质上的激发动作,只是简单地 return ActivityManager.START_CANCELED 而已。
2) 如果当初在创建这个节点时,使用者已经指定了 FLAG_ONE_SHOT 标志位的话,那么此时 sendInner() 会把这个 PendingIntentRecord 节点从 AMS 中的总表中摘除,并且把 canceled 域设为 true。而后的操作和普通激发时的动作是一致的,也就是说也会走下面的第 3)步。
3) 关于普通激发时应执行的逻辑动作是,根据当初创建 PendingIntentRecord 节点时,用户指定的 type 类型,进行不同的处理。这个 type 其实就是我们前文所说的 INTENT_SENDER_ACTIVITY、INTENT_SENDER_BROADCAST、INTENT_SENDER_SERVICE 等类型啦,大家如有兴趣,可自己参考本文一开始所说的 getActivity()、getBroadcast()、getService() 等函数的实现代码。

现在还有一个问题是,既然我们在当初获取 PendingIntent 时,已经指定了日后激发时需要递送的 intent(或 intent 数组),那么为什么 send() 动作里还有一个 intent 参数呢?它们的关系又是什么呢?我猜想,PendingIntent 机制的设计者是希望给激发端一个修改“待激发的 intent”的机会。比如当初我们获取 PendingIntent 对象时,如果在 flags 里设置了 FILL_IN_ACTION 标志位,那么就说明我们允许日后在某个激发点,用新的 intent 的 mAction 域值,替换掉我们最初给的 intent 的 mAction 域值。如果一开始没有设置 FILL_IN_ACTION 标志位,而且在最初的 intent 里已经有了非空的 mAction 域值的话,那么即使在激发端又传入了新 intent,它也不可能修改用新 intent 的 mAction 域值替换旧 intent 的 mAction 域值。

细心的读者一定记得,当初获取 PendingIntent 对象时,我们可是向 AMS 端传递了一个 intent 数组噢,虽然一般情况下这个数组里只有一个 intent 元素,但有时候我们也是有可能一次性传递多个 intent 的。比如 getActivities() 函数就可以一次传递多个 intent。可是现在激发动作 send() 却只能传递一个 intent 参数,这该如何处理呢?答案很简单,所传入的 intent 只能影响已有的 intent 数组的最后一个 intent 元素。大家可以看看 sendInner 里 allIntents[allIntents.length-1] = finalIntent;一句。

Ok,intent 说完了,下面就该做具体的激发了。我们以简单的 INTENT_SENDER_BROADCAST 型 PendingIntentRecord 来说明,此时的激发动作就是发送一个广播:

owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType,
                 finishedReceiver, code, null, null,
                 requiredPermission, (finishedReceiver != null),
                 false, UserId.getUserId(uid));

至于其他类型的 PendingIntentRecord 的激发动作,大家可以自行查阅代码,它们的基本代码格局都是差不多的。

3 小结

本文是基于我早先的一点儿笔记整理而成的。当时为了搞清楚 PendingIntent 的机理,也查阅了一些网上的相关文章,只是都不大满足我的要求,后来只好自己看代码,终于得了些自己的浅见。现在把我过去的一点儿认识整理出来,希望能对学习 PendingIntent 的同学有点儿帮助。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文