说说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的同学有点儿帮助。

时间: 2024-10-24 12:48:08

说说PendingIntent的内部机制的相关文章

SQL Server 内存中OLTP内部机制概述(一)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx ----------------------------我是分割线------------------------------- SQL S

MYSQL的InnoDB Buffer Pool内部机制

1. 基本结构:INNODB用least recently used (LRU) 算法来管理他的buffer_pool. buffer_pool在内部被分隔为两个list. a young list 和 a old list. Young list 存储那些高频使用的缓存数据(默认占整个BUFFER的5/8) Old list 存储那些低频使用的数据(默认占整个BUFFER的3/8) 2.使用机制:当一个新块数据被从磁盘缓存到buffer当中,它默认被放在Old list的头部,即midpoin

委托的内部机制

委托是一种定义方法签名的类型,是对方法的抽象.封装.与委托的签名(由返回类型和参数组成)匹配的任何可访问类和结构中的任何方法都可以分配给该委托,方法可以是静态方法,也可以是实例方法.将一个方法绑定到委托时,C#和CLR允许引用类型的协变性和逆变性. 协变性是指方法的返回类型可以派生自委托的返回类型. 逆变性是指委托的参数类型可以派生自方法的参数类型. 协变性和逆变性只能用于引用类型,不能用于值类类型或void.所以不能将下面的方法与委托绑定: delegate Object DelegateCa

搭建高可用mongodb集群(三)—— 深入副本集内部机制

http://www.lanceyan.com/tech/mongodb_repset2.html 在上一篇文章<搭建高可用mongodb集群(二)—— 副本集> 介绍了副本集的配置,这篇文章深入研究一下副本集的内部机制.还是带着副本集的问题来看吧! 副本集故障转移,主节点是如何选举的?能否手动干涉下架某一台主节点. 官方说副本集数量最好是奇数,为什么? mongodb副本集是如何同步的?如果同步不及时会出现什么情况?会不会出现不一致性? mongodb的故障转移会不会无故自动发生?什么条件会

mongodb副本集的内部机制(借鉴lanceyan.com)

针对mongodb的内部机制提出以下几个引导性的问题: 副本集故障转移,主节点是如何选举的?能否手动干涉下架某一台主节点. 官方说副本集数量最好是奇数,为什么? mongodb副本集是如何同步的?如果同步不及时会出现什么情况?会不会出现不一致性? mongodb的故障转移会不会无故自动发生?什么条件会触发?频繁触发可能会带来系统负载加重? Bully算法 mongodb副本集故障转移功能得益于它的选举机制.选举机制采用了Bully算法,可以很方便从分布式节点中选出主节点.一个分布式集群架构中一般

SQL Server 内存中OLTP内部机制概述(二)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx 译者水平有限,如有翻译不当之处,欢迎指正. ----------------------------我是分割线---------------

Android驱动学习-内部机制_回顾binder框架关键点

内部机制_回顾binder框架关键点server注册服务时, 对每个服务都提供不同的ptr/cookie,在驱动程序里对每个服务都构造一个binder_node, 它也含有ptr/cookie client使用服务前要先getService:会在驱动程序里对该服务构造一个binder_ref, binder_ref含有desc, node成员, desc是整数, node指向对应服务的binder_node 使用服务时, client构造数据,调用ioctl:数据里含有handle 驱动程序根据

SQL Server 内存中OLTP内部机制概述(三)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx 译者水平有限,如有翻译不当之处,欢迎指正. ----------------------------我是分割线---------------

SQLSERVER连接池内部机制

前言介绍: 当应用程序运行的时候,会有一个连接池的管理控件运行在应用程序的进程里,统一管理应用程序和SQLSERVER建立的所有连接, 并且维护这些连接一直处于活动状态.当有用户发出一个connection open指令时连接池会在自己维护的连接池中找一个处于空闲状态 的连接放回自己管理的连接池里,给这个用户使用.当用户使用完毕后,发出connection close指令,连接池会把这个连接放回自己 管理的连接池里,让他重新处于空闲状态,而不是真的从SQL里登出.这样如果下次有用户需要相同连接,