理解PendingIntent

一 概述

在 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,并递送出去。

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

二 PendingIntent原理

2.1 发起端获取PendingIntent

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

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

  • 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,
            @NonNull Intent intent, @Flags int flags, @Nullable Bundle options) {
        String packageName = context.getPackageName();
        String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
                context.getContentResolver()) : null;
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(context);
            IIntentSender target =
                ActivityManager.getService().getIntentSender(
                    ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
                    null, null, requestCode, new Intent[] { intent },
                    resolvedType != null ? new String[] { resolvedType } : null,
                    flags, options, context.getUserId());
            return target != null ? new PendingIntent(target) : null;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

其中那句 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 里又有什么信息呢?它的定义截选如下:

public final class PendingIntentRecord extends IIntentSender.Stub {
    ......
    final PendingIntentController controller;
    final Key key; // 最关键的key域
    final int uid;
    public final WeakReference<PendingIntentRecord> ref;
    boolean sent = false;
    boolean canceled = false;
    ......
    String stringName;
    String lastTagPrefix;
    String lastTag;

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

        private static final int ODD_PRIME_NUMBER = 37;

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

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

我们再看 Key 的构造函数,可以看到 Key 不光承担着记录信息的作用,它还承担“键值”的作用。

2.3 AMS中的PendingIntentRecord总表

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

PendingIntentController.java

final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>
        mIntentSenderRecords = new HashMap<>();

这张哈希映射表的键值类型就是刚才所说的 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,
            @NonNull Intent intent, @Flags int flags, @Nullable Bundle options) {
        String packageName = context.getPackageName();
        String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
                context.getContentResolver()) : null;
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(context);
            IIntentSender target =
                ActivityManager.getService().getIntentSender(
                    ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
                    null, null, requestCode, new Intent[] { intent },
                    resolvedType != null ? new String[] { resolvedType } : null,
                    flags, options, context.getUserId());
            return target != null ? new PendingIntent(target) : null;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
}
  • context 参数是调用方的上下文
  • requestCode 是个简单的整数,起区分作用
  • intent 是异步激发时将发出的 intent
  • flags 可以包含一些既有的标识,比如 FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT 等等
  • options 可以携带一些额外的数据

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

ActivityManagerService.java

public IIntentSender getIntentSender(int type,
            String packageName, IBinder token, String resultWho,
            int requestCode, Intent[] intents, String[] resolvedTypes,
            int flags, Bundle bOptions, int userId) 
  • 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() 的代码:

Activity.java

public PendingIntent createPendingResult(int requestCode, @NonNull Intent data,
            @PendingIntent.Flags int flags) {
        String packageName = getPackageName();
        try {
            data.prepareToLeaveProcess(this);
            IIntentSender target =
                ActivityManager.getService().getIntentSender(
                        ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
                        mParent == null ? mToken : mParent.mToken,
                        mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null,
                        getUserId());
            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.java

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.java

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()函数

ActivityManagerService.java

public IIntentSender getIntentSender(int type,
            String packageName, IBinder token, String resultWho,
            int requestCode, Intent[] intents, String[] resolvedTypes,
            int flags, Bundle bOptions, int userId) {

        // NOTE: The service lock isn't held in this method because 
        // nothing in the method requires the service lock to be held.

        enforceNotIsolatedCaller("getIntentSender");
        // Refuse possible leaked file descriptors
        if (intents != null) {
            if (intents.length < 1) {
                ......
            }
            for (int i=0; i<intents.length; i++) {
                Intent intent = intents[i];
                if (intent != null) {
                    if (intent.hasFileDescriptors()) {
                        ......
                    }
                    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);
                }
            }
            ......
        }
        ......
        try {
            ......

            if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
                return mAtmInternal.getIntentSender(type, packageName, ......);
            }
            //关键点:调用 PendingIntentController 的 getIntentSender
            // AMS 把获取 PendingIntent 的任务交给了 PendingIntentController
            return mPendingIntentController.getIntentSender(type, packageName,
                callingUid, userId, token, resultWho, requestCode, 
                intents, resolvedTypes, flags, bOptions);
        } catch (RemoteException e) {
            throw new SecurityException(e);
        }
    }

其中有一段逐条判断 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() 中最核心的一句应该是 mPendingIntentController.getIntentSender。即调用 PendingIntentController 的 getIntentSender

2.4.2 PendingIntentController.getIntentSender

public PendingIntentRecord getIntentSender(int type, String packageName, 
    int callingUid, int userId, IBinder token, String resultWho, 
    int requestCode, Intent[] intents, String[] resolvedTypes,
    int flags, Bundle bOptions) {
        synchronized (mLock) {
            if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSender(): uid=" + callingUid);
            ......
        //各种 flag 的作用
        final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
        final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0;
        final boolean updateCurrent = (flags & PendingIntent.FLAG_UPDATE_CURRENT) != 0;
        flags &= ~(PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_CANCEL_CURRENT
                    | PendingIntent.FLAG_UPDATE_CURRENT);

     //关键点:生成 Key,把 intents 传入 key 中
        PendingIntentRecord.Key key = 
            new PendingIntentRecord.Key(type, packageName, token,
                    resultWho, requestCode, intents, resolvedTypes, flags,
                    SafeActivityOptions.fromBundle(bOptions), userId);
            WeakReference<PendingIntentRecord> ref;
            ref = mIntentSenderRecords.get(key);
            PendingIntentRecord rec = ref != null ? ref.get() : null;
            // 找到了匹配的 PendingIntentRecord,现在考虑要不要更新它,或者取消它
            if (rec != null) {
                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,则从哈希映射表中删除之
                makeIntentSenderCanceled(rec);
                mIntentSenderRecords.remove(key);
            }
            if (noCreate) {
            // 如果明确表示了不创建新节点,也就是说标志中带有FLAG_NO_CREATE,
        // 那么不管是不是Cancel了PendingIntent,此时一概直接返回
                return rec;
            }
            // 从哈希映射表中找不到,而且又没有写明FLAG_NO_CREATE,此时创建一个新节点
            //创建一个新的PendingIntentRecord添加到mIntentSenderRecords中
         rec = new PendingIntentRecord(this, key, callingUid);
         mIntentSenderRecords.put(key, rec.ref);
         return rec;
    }
}

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

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

2.4.3 说说flags

通过以上代码,我们应该明白了 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 标识,它并没有在 getIntentSender() 中露脸儿。它的名字是 “FLAG_ONE_SHOT”,也就是“只打一枪”的意思,那么很明显,这个标识起作用的地方应该是在“激发”函数里。在最终的激发函数(sendInner())里,我们可以看到下面的代码:

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

public int sendInner(int code, Intent intent, String resolvedType,
    IBinder whitelistToken, IIntentReceiver finishedReceiver,
    String requiredPermission, IBinder resultTo, String resultWho,
    int requestCode, int flagsMask, int flagsValues, Bundle options) {
        if (intent != null) intent.setDefusable(true);
        if (options != null) options.setDefusable(true);

       ......
        synchronized (controller.mLock) {
            if (canceled) {
                return ActivityManager.START_CANCELED;
            }

            sent = true;
            if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
            //关键点:调用 PendingIntentController 的 cancelIntentSender
                controller.cancelIntentSender(this, true);
            }
            ......
        }
        ......
    }

PendingIntentController.java

public void cancelIntentSender(IIntentSender sender) {
        if (!(sender instanceof PendingIntentRecord)) {
            return;
        }
        synchronized (mLock) {
            final PendingIntentRecord rec = (PendingIntentRecord) sender;
            try {
                ......
                }
            } catch (RemoteException e) {
                throw new SecurityException(e);
            }
            cancelIntentSender(rec, true);
        }
    }

    public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity) {
        synchronized (mLock) {
        //设置 canceled 域设为 true
            makeIntentSenderCanceled(rec);
            mIntentSenderRecords.remove(rec.key);
            if (cleanActivity && rec.key.activity != null) {
                final Message m = PooledLambda.obtainMessage(
                        PendingIntentController::clearPendingResultForActivity, this,
                        rec.key.activity, rec.ref);
                mH.sendMessage(m);
            }
        }
    }
private void makeIntentSenderCanceled(PendingIntentRecord rec) {
        rec.canceled = true; //设置 canceled 域设为 true
        final RemoteCallbackList<IResultReceiver> callbacks = 
        rec.detachCancelListenersLocked();
        if (callbacks != null) {
         final Message m = PooledLambda.obtainMessage(
         PendingIntentController::handlePendingIntentCancelled, this, callbacks);
        mH.sendMessage(m);
        }
        final AlarmManagerInternal ami = 
        LocalServices.getService(AlarmManagerInternal.class);
        ami.remove(new PendingIntent(rec));
    }

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

2.5 PendingIntent的激发动作

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

public void send(Context context, int code, @Nullable Intent intent,
            @Nullable OnFinished onFinished, @Nullable Handler handler,
            @Nullable String requiredPermission, @Nullable Bundle options)
            throws CanceledException {
            //关键点:调用 sendAndReturnResult
        if (sendAndReturnResult(context, code, intent, onFinished, handler,
        requiredPermission,
                options) < 0) {
            throw new CanceledException();
        }
    }


public int sendAndReturnResult(Context context, int code, @Nullable Intent intent,
            @Nullable OnFinished onFinished, @Nullable Handler handler,
            @Nullable String requiredPermission, @Nullable Bundle options)
            throws CanceledException {
        try {
            String resolvedType = intent != null ?
                    intent.resolveTypeIfNeeded(context.getContentResolver())
                    : null;
            //关键点:调用AMS的sendIntentSender
            //注意其中的 mTarget 参数 就是IIntentSender代理
            //对应着AMS中的一个Binder实体:PendingIntentRecord
            return ActivityManager.getService().sendIntentSender(
                    mTarget, mWhitelistToken, code, intent, resolvedType,
                    onFinished != null
                            ? new FinishedDispatcher(this, onFinished, handler)
                            : null,
                    requiredPermission, options);
        } catch (RemoteException e) {
            throw new CanceledException(e);
        }
    }

ActivityManagerService.java

public int sendIntentSender(IIntentSender target, IBinder whitelistToken, int code,
            Intent intent, String resolvedType,
            IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
        if (target instanceof PendingIntentRecord) {//走到此分支
            return ((PendingIntentRecord)target).sendWithResult(code, intent, resolvedType,
                    whitelistToken, finishedReceiver, requiredPermission, options);
        } else {
            if (intent == null) {
                ......
                Slog.wtf(TAG, "Can't use null intent with direct IIntentSender call");
                intent = new Intent(Intent.ACTION_MAIN);
            }
            try {
                target.send(code, intent, resolvedType, whitelistToken, null,
                        requiredPermission, options);
            } catch (RemoteException e) {
            }
            ......
            if (finishedReceiver != null) {
                try {
                    finishedReceiver.performReceive(intent, 0,
                            null, null, false, false, UserHandle.getCallingUserId());
                } catch (RemoteException e) {
                }
            }
            return 0;
        }
    }

PendingIntentRecord.java

public int sendWithResult(int code, Intent intent, String resolvedType,
    IBinder whitelistToken, IIntentReceiver finishedReceiver,
    String requiredPermission, Bundle options) {
        return sendInner(code, intent, resolvedType, whitelistToken, finishedReceiver,
                requiredPermission, null, null, 0, 0, 0, options);
    }

    public int sendInner(int code, Intent intent, String resolvedType,
        IBinder whitelistToken, IIntentReceiver finishedReceiver,
        String requiredPermission, IBinder resultTo, String resultWho,
        int requestCode, int flagsMask, int flagsValues, Bundle options) {
        if (intent != null) intent.setDefusable(true);
        if (options != null) options.setDefusable(true);

        ......
        synchronized (controller.mLock) {
            if (canceled) {
                return ActivityManager.START_CANCELED;
            }

            sent = true;
            if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
                controller.cancelIntentSender(this, true);
            }

            finalIntent = key.requestIntent != null ?
            new Intent(key.requestIntent) : new Intent();

            ......
            final ActivityOptions opts = ActivityOptions.fromBundle(options);
            if (opts != null) {
                finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
            }

            ......
            if (key.type == ActivityManager.INTENT_SENDER_ACTIVITY
                    && key.allIntents != null && key.allIntents.length > 1) {
              allIntents = new Intent[key.allIntents.length];
             allResolvedTypes = new String[key.allIntents.length];
             System.arraycopy(key.allIntents, 0, allIntents, 0, key.allIntents.length);
              if (key.allResolvedTypes != null) {
                 System.arraycopy(key.allResolvedTypes, 0, allResolvedTypes, 0,
                            key.allResolvedTypes.length);
                }
                //关键点
                allIntents[allIntents.length - 1] = finalIntent;
                allResolvedTypes[allResolvedTypes.length - 1] = resolvedType;
            }

        }

      ......
        int res = START_SUCCESS;
        try {
            ......

            boolean sendFinish = finishedReceiver != null;
            int userId = key.userId;
            if (userId == UserHandle.USER_CURRENT) {
                userId = controller.mUserController.getCurrentOrTargetUserId();
            }
            final boolean allowTrampoline = uid != callingUid
                    && controller.mAtmInternal.isUidForeground(callingUid);
            switch (key.type) {
                case ActivityManager.INTENT_SENDER_ACTIVITY:
                    try {
                        if (key.allIntents != null && key.allIntents.length > 1) {
                        //关键点,调用ATM执行activity的启动工作
                       res = controller.mAtmInternal.startActivitiesInPackage(
                          uid, callingPid, callingUid, key.packageName, allIntents,
                          allResolvedTypes, resultTo, mergedOptions, userId,
                          false /* validateIncomingUser */,
                          this /* originatingPendingIntent */,
mAllowBgActivityStartsForActivitySender.contains(whitelistToken));
                        } else {
                      ......
                        }
                    } catch (RuntimeException e) {
                        Slog.w(TAG, "Unable to send startActivity intent", e);
                    }
                    break;
                ......
            }

            if (sendFinish && res != ActivityManager.START_CANCELED) {
                try {
                    finishedReceiver.performReceive(new Intent(finalIntent), 0,
                            null, null, false, false, key.userId);
                } catch (RemoteException e) {
                }
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }

        return res;
    }

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

  • 如果当前 PendingIntentRecord 节点已经处于 canceled 域 为true 的状态,那么说明这个节点已经被取消掉了,此时 sendInner() 不会做任何实质上的激发动作,只是简单地 return ActivityManager.START_CANCELED 而已
  • 如果当初在创建这个节点时,使用者已经指定了 FLAG_ONE_SHOT 标志位的话,那么此时 sendInner() 会把这个 PendingIntentRecord 节点从 AMS 中的总表中摘除,并且把 canceled 域设为 true。而后的操作和普通激发时的动作是一致的,也就是说也会走下步
  • 关于普通激发时应执行的逻辑动作是,根据当初创建 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; 一句。

三 小结及使用场景

大体的原理是: A应用希望让B应用帮忙触发一个行为,这是跨应用的通信,需要 Android 系统作为中间人,这里的中间人就是 ActivityManager。 A应用创建建 PendingIntent,在创建 PendingIntent 的过程中,向 ActivityManager 注册了这个 PendingIntent,所以,即使A应用死了,当它再次苏醒时,只要提供相同的参数,还是可以获取到之前那个 PendingIntent 的。当 A 将 PendingIntent 调用系统 API 比如 AlarmManager.set(),实际是将权限给了B应用,这时候, B应用可以根据参数信息,来从 ActivityManager 获取到 A 设置的 PendingIntent。

已知的使用场景是:

  • 通知,在点击通知时执行调起本应用的操作,当然也可以执行其他操作
  • 闹钟,定时执行某个操作
  • 桌面小部件,点击小部件时执行某个操作

通知,闹钟,桌面小部件,都是运行在其他应用中的,但是给我们的感知就像是我们自己的应用的一部分。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值