PendingIntent的內(nèi)部機(jī)制

轉(zhuǎn)自https://my.oschina.net/youranhongcha/blog/196933

1 概述
在Android中着绷,我們常常使用PendingIntent來表達(dá)一種“留待日后處理”的意思。從這個(gè)角度來說余指,PendingIntent可以被理解為一種特殊的異步處理機(jī)制。不過指厌,單就命名而言虑椎,PendingIntent其實(shí)具有一定誤導(dǎo)性,因?yàn)樗炔焕^承于Intent易桃,也不包含Intent,它的核心可以粗略地匯總成四個(gè)字——“異步激發(fā)”锌俱。
很明顯晤郑,這種異步激發(fā)常常是要跨進(jìn)程執(zhí)行的。比如說A進(jìn)程作為發(fā)起端贸宏,它可以從系統(tǒng)“獲取”一個(gè)PendingIntent造寝,然后A進(jìn)程可以將PendingIntent對(duì)象通過binder機(jī)制“傳遞”給B進(jìn)程,再由B進(jìn)程在未來某個(gè)合適時(shí)機(jī)吭练,“回調(diào)”PendingIntent對(duì)象的send()動(dòng)作诫龙,完成激發(fā)。
在Android系統(tǒng)中鲫咽,最適合做集中性管理的組件就是AMS(Activity Manager Service)啦签赃,所以它義不容辭地承擔(dān)起管理所有PendingIntent的職責(zé)。這樣我們就可以畫出如下示意圖:


注意其中的第4步“遞送相應(yīng)的intent”分尸。這一步遞送的intent是從何而來的呢锦聊?簡單地說,當(dāng)發(fā)起端獲取PendingIntent時(shí)箩绍,其實(shí)是需要同時(shí)提供若干intent的孔庭。這些intent和PendingIntent只是配套的關(guān)系,而不是聚合的關(guān)系材蛛,它們會(huì)被緩存在AMS中圆到。日后怎抛,一旦處理端將PendingIntent的“激發(fā)”語義傳遞到AMS,AMS就會(huì)嘗試找到與這個(gè)PendingIntent對(duì)應(yīng)的若干intent芽淡,并遞送出去抽诉。
當(dāng)然,以上說的只是大概情況吐绵,實(shí)際的技術(shù)細(xì)節(jié)會(huì)更復(fù)雜一點(diǎn)兒。下面我們就來談?wù)劶?xì)節(jié)河绽。

2 PendingIntent的技術(shù)細(xì)節(jié)
2.1 發(fā)起端獲取PendingIntent
我們先要理解己单,所謂的“發(fā)起端獲取PendingIntent”到底指的是什么。難道只是簡單new一個(gè)PendingIntent對(duì)象嗎耙饰?當(dāng)然不是纹笼。此處的“獲取”動(dòng)作其實(shí)還含有向AMS“注冊(cè)”intent的語義。
在PendingIntent.java文件中苟跪,我們可以看到有如下幾個(gè)比較常見的靜態(tài)函數(shù):

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的動(dòng)作了廷痘。
坦白說,這幾個(gè)函數(shù)的命名可真不怎么樣件已,所以我們簡單解釋一下笋额。上面的getActivity()的意思其實(shí)是,獲取一個(gè)PendingIntent對(duì)象篷扩,而且該對(duì)象日后激發(fā)時(shí)所做的事情是啟動(dòng)一個(gè)新activity兄猩。也就是說,當(dāng)它異步激發(fā)時(shí)鉴未,會(huì)執(zhí)行類似Context.startActivity()那樣的動(dòng)作枢冤。相應(yīng)地,getBroadcast()和getService()所獲取的PendingIntent對(duì)象在激發(fā)時(shí)铜秆,會(huì)分別執(zhí)行類似Context.sendBroadcast()和Context.startService()這樣的動(dòng)作淹真。至于最后兩個(gè)getActivities(),用得比較少连茧,激發(fā)時(shí)可以啟動(dòng)幾個(gè)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); 
        IntentSender 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) {
        e.printStackTrace();
    } 
    return null;
}

其中那句new PendingIntent(target)創(chuàng)建了PendingIntent對(duì)象,其重要性自不待言梅屉。然而值纱,這個(gè)對(duì)象的內(nèi)部核心其實(shí)是由上面那個(gè)getIntentSender()函數(shù)得來的。而這個(gè)IntentSender核心才是我們真正需要關(guān)心的東西坯汤。
說穿了虐唠,此處的IntentSender對(duì)象是個(gè)binder代理,它對(duì)應(yīng)的binder實(shí)體是AMS中的PendingIntentRecord對(duì)象惰聂。PendingIntent對(duì)象構(gòu)造之時(shí)疆偿,IntentSender代理作為參數(shù)傳進(jìn)來咱筛,并記錄在PendingIntent的mTarget域。日后杆故,當(dāng)PendingIntent執(zhí)行異步激發(fā)時(shí)迅箩,其內(nèi)部就是靠這個(gè)mTarget域向AMS傳遞語義的。
我們前文說過处铛,PendingIntent常常會(huì)經(jīng)由binder機(jī)制饲趋,傳遞到另一個(gè)進(jìn)程去。而binder機(jī)制可以保證撤蟆,目標(biāo)進(jìn)程得到的PendingIntent的mTarget域也是合法的IntentSender代理奕塑,而且和發(fā)起端的IntentSender代理對(duì)應(yīng)著同一個(gè)PendingIntentRecord實(shí)體。示意圖如下:

2.2 AMS里的PendingIntentRecord
那么PendingIntentRecord里又有什么信息呢家肯?它的定義截選如下:

class PendingIntentRecord extends IIntentSender.Stub {
    final ActivityManagerService owner; 
    final Key key; // 最關(guān)鍵的key域 
    final int uid; 
    final WeakReference<PendingIntentRecord> ref; 
    boolean sent = false; 
    boolean canceled = false; 
    String stringName; 
}

請(qǐng)注意其中那個(gè)key域龄砰。這里的Key是個(gè)PendingIntentRecord的內(nèi)嵌類,其定義截選如下:

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; // 注意换棚!記錄了當(dāng)初獲取PendingIntent時(shí),用戶所指定的所有
    intent String[] allResolvedTypes; 
    final int flags; 
    final int hashCode; 
}

請(qǐng)注意其中的allIntents[]數(shù)組域以及requestIntent域反镇。前者記錄了當(dāng)初獲取PendingIntent時(shí)固蚤,用戶所指定的所有intent(雖然一般情況下只會(huì)指定一個(gè)intent,但類似getActivities()這樣的函數(shù)還是可以指定多個(gè)intent的)歹茶,而后者可以粗淺地理解為用戶所指定的那個(gè)intent數(shù)組中的最后一個(gè)intent∑睦現(xiàn)在大家應(yīng)該清楚異步激發(fā)時(shí)用到的intent都存在哪里了吧。
Key的構(gòu)造函數(shù)截選如下:

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數(shù)組中的最后一個(gè) 
    requestResolvedType = _it != null ? _it[_it.length-1] : null; 
    allIntents = _i; 
    // 所有intent allResolvedTypes = _it; 
    flags = _f; 
    options = _o; 
}

Key不光承擔(dān)著記錄信息的作用辆亏,它還承擔(dān)“鍵值”的作用风秤。

2.3 AMS中的PendingIntentRecord總表
在AMS中,管理著系統(tǒng)中所有的PendingIntentRecord節(jié)點(diǎn)扮叨,所以需要把這些節(jié)點(diǎn)組織成一張表:

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

這張哈希映射表的鍵值類型就是剛才所說的PendingIntentRecord.Key缤弦。
以后每當(dāng)我們要獲取PendingIntent對(duì)象時(shí),PendingIntent里的mTarget是這樣得到的:AMS會(huì)先查mIntentSenderRecords表彻磁,如果能找到符合的PendingIntentRecord節(jié)點(diǎn)碍沐,則返回之。如果找不到衷蜓,就創(chuàng)建一個(gè)新的PendingIntentRecord節(jié)點(diǎn)累提。因?yàn)镻endingIntentRecord是個(gè)binder實(shí)體,所以經(jīng)過binder機(jī)制傳遞后磁浇,客戶進(jìn)程拿到的就是個(gè)合法的binder代理斋陪。如此一來,前文的示意圖可以進(jìn)一步修改成下圖:


2.4 AMS里的getIntentSender()函數(shù)
現(xiàn)在,我們回過頭繼續(xù)說前文的getActivity()无虚,以及其調(diào)用的getIntentSender()缔赠。我們先列一遍getActivity()的原型:

public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags, Bundle options)

context參數(shù)是調(diào)用方的上下文。
requestCode是個(gè)簡單的整數(shù)友题,起區(qū)分作用嗤堰。
intent是異步激發(fā)時(shí)將發(fā)出的intent。
flags可以包含一些既有的標(biāo)識(shí)度宦,比如FLAG_ONE_SHOT踢匣、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT戈抄、FLAG_UPDATE_CURRENT等等符糊。不少同學(xué)對(duì)這個(gè)域不是很清楚,我們后文會(huì)細(xì)說呛凶。
options可以攜帶一些額外的數(shù)據(jù)。
getActivity()的代碼很簡單行贪,其參數(shù)基本上都傳給了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;

其參數(shù)比getActivity()要多一些,我們逐個(gè)說明建瘫。
type參數(shù)表明PendingIntent的類型崭捍。getActivity()和getActivities()動(dòng)作里指定的類型值是INTENT_SENDER_ACTIVITY,getBroadcast()和getService()和動(dòng)作里指定的類型值分別是INTENT_SENDER_BROADCAST和INTENT_SENDER_SERVICE啰脚。另外殷蛇,在Activity.java文件中,我們還看到一個(gè)createPendingResult()函數(shù)橄浓,這個(gè)函數(shù)表達(dá)了發(fā)起方的activity日后希望得到result回饋的意思粒梦,所以其內(nèi)部調(diào)用getIntentSender()時(shí)指定的類型值為INTENT_SENDER_ACTIVITY_RESULT。
packageName參數(shù)表示發(fā)起端所屬的包名荸实。
token參數(shù)是個(gè)指代回饋目標(biāo)方的代理匀们。這是什么意思呢?我們常用的getActivity()准给、getBroadcast()和getService()中泄朴,只是把這個(gè)參數(shù)簡單地指定為null,表示這個(gè)PendingIntent激發(fā)時(shí)露氮,是不需要發(fā)回什么回饋的祖灰。不過當(dāng)我們希望獲取類型為INTENT_SENDER_ACTIVITY_RESULT的PendingIntent時(shí),就需要指定token參數(shù)了畔规。具體可參考createPendingResult()的代碼:

public PendingIntent createPendingResult(int requestCode, Intent data, int flags) { 
    String packageName = getPackageName(); 
    try { 
        data.setAllowFds(false); 
        IntentSender 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。說得簡單點(diǎn)兒,AMS內(nèi)部可以根據(jù)這個(gè)token找到其對(duì)應(yīng)的ActivityRecord详民,日后當(dāng)PendingIntent激發(fā)時(shí)延欠,AMS可以根據(jù)這個(gè)ActivityRecord確定出該向哪個(gè)目標(biāo)進(jìn)程的哪個(gè)Activity發(fā)出result語義。
resultWho參數(shù)和token參數(shù)息息相關(guān)沈跨,一般也是null啦由捎。在createPendingResult()中,其值為Activity的mEmbeddedID字符串饿凛。
requestCode參數(shù)是個(gè)簡單的整數(shù)狞玛,可以在獲取PendingIntent時(shí)由用戶指定,它可以起區(qū)分的作用涧窒。
intents數(shù)組參數(shù)是異步激發(fā)時(shí)希望發(fā)出的intent心肪。對(duì)于getActivity()、getBroadcast()和getService()來說纠吴,都只會(huì)指定一個(gè)intent而已硬鞍。只有g(shù)etActivities()會(huì)嘗試一次傳入若干intent。
resolvedTypes參數(shù)基本上和intent是相關(guān)的戴已。一般是這樣得到的:

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

這個(gè)值常常和intent內(nèi)部的mData URI有關(guān)系固该,比如最終的值可能是URI對(duì)應(yīng)的MIME類型。
flags參數(shù)可以指定PendingIntent的一些行為特點(diǎn)糖儡。它的取值是一些既有的比特標(biāo)識(shí)的組合伐坏。目前可用的標(biāo)識(shí)有:FLAG_ONE_SHOT、FLAG_NO_CREATE握联、FLAG_CANCEL_CURRENT桦沉、FLAG_UPDATE_CURRENT等等。有時(shí)候金闽,flags中還可以附帶若干FILL_IN_XXX標(biāo)識(shí)纯露。我們把常見的標(biāo)識(shí)定義列舉如下:
【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_打頭的標(biāo)志位,主要是在intent對(duì)象的fillIn()函數(shù)里起作用:
public int fillIn(Intent other, int flags)

我們以FILL_IN_ACTION為例來說明代芜,當(dāng)我們執(zhí)行類似srcIntent.fillIn(otherIntent, ...)的句子時(shí)苔埋,如果otherIntent的mAction域不是null值,那么fillIn()會(huì)在以下兩種情況下蜒犯,用otherIntent的mAction域值為srcIntent的mAction域賦值:
1) 當(dāng)srcIntent的mAction域值為null時(shí)组橄;
2) 如果fillIn的flags參數(shù)里攜帶了FILL_IN_ACTION標(biāo)志位,那么即便srcIntent的mAction已經(jīng)有值了罚随,此時(shí)也會(huì)用otherIntent的mAction域值強(qiáng)行替換掉srcIntent的mAction域值玉工。
其他FILL_IN_標(biāo)志位和FILL_IN_ACTION的處理方式類似,我們不再贅述淘菩。
options參數(shù)可以攜帶一些額外數(shù)據(jù)遵班。

2.4.1 getIntentSender()函數(shù)
getIntentSender()函數(shù)摘錄如下:

public IntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) { 
    // 先判斷intents數(shù)組屠升,可以用偽代碼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()函數(shù)中有一段逐條判斷intents[]的代碼,我用偽代碼checkIntents(intents)來表示狭郑,這部分對(duì)應(yīng)的實(shí)際代碼如下:

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對(duì)象時(shí)腹暖,intent中是不能攜帶文件描述符的。而且如果這個(gè)PendingIntent是那種要發(fā)出廣播的PendingIntent翰萨,那么intent中也不能攜帶FLAG_RECEIVER_BOOT_UPGRADE標(biāo)識(shí)符脏答。“BOOT_UPGRADE”應(yīng)該是“啟動(dòng)并升級(jí)”的意思亩鬼,它不能使用PendingIntent殖告。
getIntentSender()中最核心的一句應(yīng)該是調(diào)用getIntentSenderLocked()的那句。

2.4.2 getIntentSenderLocked()函數(shù)
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對(duì)應(yīng)的PendingIntentRecord黄绩,如果找不到就創(chuàng)建一個(gè)新的節(jié)點(diǎn)。 
    WeakReference<PendingIntentRecord> ref; 
    ref = mIntentSenderRecords.get(key); 
    PendingIntentRecord rec = ref != null ? ref.get() : null; 
    if (rec != null) { 
        // 找到了匹配的PendingIntent玷过,現(xiàn)在考慮要不要更新它爽丹,或者取消它。 
        if (!cancelCurrent) {
            if (updateCurrent) { 
                // 如果明確指定了FLAG_UPDATE_CURRENT辛蚊,那么更新找到的節(jié)點(diǎn) 
                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; 
                } 
            } 
            // 凡是能找到對(duì)應(yīng)的節(jié)點(diǎn)粤蝎,而且又不取消該節(jié)點(diǎn)的,那么就return這個(gè)節(jié)點(diǎn) 
            return rec; 
        } 
        // 如果PendingIntent的標(biāo)志中帶有FLAG_CANCEL_CURRENT嚼隘,則從哈希映射表中刪除之 
        rec.canceled = true; 
        mIntentSenderRecords.remove(key); 
    } 
    if (noCreate) { 
        // 如果明確表示了不創(chuàng)建新節(jié)點(diǎn),也就是說標(biāo)志中帶有FLAG_NO_CREATE袒餐,那么不管是不是Cancel了PendingIntent飞蛹,此時(shí)一概直接返回。 
        return rec; 
    } 
    // 從哈希映射表中找不到灸眼,而且又沒有寫明FLAG_NO_CREATE卧檐,此時(shí)創(chuàng)建一個(gè)新節(jié)點(diǎn) 
    rec = new PendingIntentRecord(this, key, callingUid);     
    mIntentSenderRecords.put(key, rec.ref); 
    if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { 
    // 如果intent需要返回結(jié)果,那么修改token對(duì)應(yīng)的ActivityRecord的pendingResults域焰宣。 
        if (activity.pendingResults == null) { 
            activity.pendingResults = new HashSet<WeakReference<PendingIntentRecord>>(); 
        } 
        activity.pendingResults.add(rec.ref); 
    } 
    return rec;
}

上面這段代碼主要做的事情有:
1)將傳進(jìn)來的多個(gè)參數(shù)信息整理成一個(gè)PendingIntentRecord.Key對(duì)象(key)霉囚;
2)嘗試從mIntentSenderRecords總表中查找和key相符的PendingIntentRecord節(jié)點(diǎn);
3)根據(jù)flags參數(shù)所含有的意義匕积,對(duì)得到的PendingIntentRecord進(jìn)行加工盈罐。有時(shí)候修改之,有時(shí)候刪除之闪唆。
4)如果在總表中沒有找到對(duì)應(yīng)的PendingIntentRecord節(jié)點(diǎn)盅粪,或者根據(jù)flags的語義刪除了剛找到的節(jié)點(diǎn),那么此時(shí)的默認(rèn)行為是創(chuàng)建一個(gè)新的PendingIntentRecord節(jié)點(diǎn)悄蕾,并插入總表票顾。除非flags中明確指定了FLAG_NO_CREATE,此時(shí)不會(huì)創(chuàng)建新節(jié)點(diǎn)。

2.4.3 說說flags
從getIntentSenderLocked()的代碼中奠骄,我們終于搞明白了flags中那些特定比特值的意義了豆同。我們現(xiàn)在總結(jié)一下。
應(yīng)該說這些flags比特值基本上都是在圍繞著mIntentSenderRecords總表說事的含鳞。其中影锈,F(xiàn)LAG_CANCEL_CURRENT的意思是,當(dāng)我們獲取PendingIntent時(shí)民晒,如果可以從總表中查到一個(gè)相符的已存在的PendingIntentRecord節(jié)點(diǎn)的話精居,那么需要把這個(gè)節(jié)點(diǎn)從總表中清理出去。而在沒有指定FLAG_CANCEL_CURRENT的大前提下潜必,如果用戶指定了FLAG_UPDATE_CURRENT標(biāo)識(shí)靴姿,那么會(huì)用新的intents參數(shù)替掉剛查到的PendingIntentRecord中的舊intents。
而不管是剛清理了已存在的PendingIntentRecord磁滚,還是壓根兒就沒有找到符合的PendingIntentRecord佛吓,只要用戶沒有明確指定FLAG_NO_CREATE標(biāo)識(shí),系統(tǒng)就會(huì)盡力創(chuàng)建一個(gè)新的PendingIntentRecord節(jié)點(diǎn)垂攘,并插入總表维雇。
至于FLAG_ONE_SHOT標(biāo)識(shí)嘛,它并沒有在getIntentSenderLocked()中露臉兒晒他。它的名字是“FLAG_ONE_SHOT”吱型,也就是“只打一槍”的意思,那么很明顯陨仅,這個(gè)標(biāo)識(shí)起作用的地方應(yīng)該是在“激發(fā)”函數(shù)里津滞。在最終的激發(fā)函數(shù)(sendInner())里,我們可以看到下面的代碼:
【frameworks/base/services/java/com/android/server/am/PendingIntentRecord.java】

int sendInner(int code, Intent intent, String resolvedType, IntentReceiver 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;
}

意思很簡單灼伤,一進(jìn)行激發(fā)就把相應(yīng)的PendingIntentRecord節(jié)點(diǎn)從總表中清理出去触徐,而且把PendingIntentRecord的canceled域設(shè)為true。這樣狐赡,以后即便外界再調(diào)用send()動(dòng)作都沒用了撞鹉,因?yàn)樵僖矡o法進(jìn)入if (!canceled)判斷了。

2.4.4 將PendingIntentRecord節(jié)點(diǎn)插入總表
接下來getIntentSenderLocked()函數(shù)new了一個(gè)PendingIntentRecord節(jié)點(diǎn)颖侄,并將之插入mIntentSenderRecords總表中鸟雏。

2.5 PendingIntent的激發(fā)動(dòng)作
下面我們來看PendingIntent的激發(fā)動(dòng)作。在前文我們已經(jīng)說過览祖,當(dāng)需要激發(fā)PendingIntent之時(shí)崔慧,主要是通過調(diào)用PendingIntent的send()函數(shù)來完成激發(fā)動(dòng)作的。PendingIntent提供了多個(gè)形式的send()函數(shù)穴墅,然而這些函數(shù)的內(nèi)部其實(shí)調(diào)用的是同一個(gè)send()惶室,其函數(shù)原型如下:

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

該函數(shù)內(nèi)部最關(guān)鍵的一句是:

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

我們前文已經(jīng)介紹過這個(gè)mTarget域了温自,它對(duì)應(yīng)著AMS中的某個(gè)PendingIntentRecord。
所以我們要看一下PendingIntentRecord一側(cè)的send()函數(shù)皇钞,其代碼如下:

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()才是真正做激發(fā)動(dòng)作的函數(shù)悼泌。
sendInner()完成的主要邏輯動(dòng)作有:
1) 如果當(dāng)前PendingIntentRecord節(jié)點(diǎn)已經(jīng)處于canceled域?yàn)閠rue的狀態(tài),那么說明這個(gè)節(jié)點(diǎn)已經(jīng)被取消掉了夹界,此時(shí)sendInner()不會(huì)做任何實(shí)質(zhì)上的激發(fā)動(dòng)作馆里,只是簡單地return ActivityManager.START_CANCELED而已。 2) 如果當(dāng)初在創(chuàng)建這個(gè)節(jié)點(diǎn)時(shí)可柿,使用者已經(jīng)指定了FLAG_ONE_SHOT標(biāo)志位的話鸠踪,那么此時(shí)sendInner()會(huì)把這個(gè)PendingIntentRecord節(jié)點(diǎn)從AMS中的總表中摘除,并且把canceled域設(shè)為true复斥。而后的操作和普通激發(fā)時(shí)的動(dòng)作是一致的营密,也就是說也會(huì)走下面的第3)步。 3) 關(guān)于普通激發(fā)時(shí)應(yīng)執(zhí)行的邏輯動(dòng)作是目锭,根據(jù)當(dāng)初創(chuàng)建PendingIntentRecord節(jié)點(diǎn)時(shí)评汰,用戶指定的type類型,進(jìn)行不同的處理痢虹。這個(gè)type其實(shí)就是我們前文所說的INTENT_SENDER_ACTIVITY被去、INTENT_SENDER_BROADCAST、INTENT_SENDER_SERVICE等類型啦奖唯,大家如有興趣惨缆,可自己參考本文一開始所說的getActivity()、getBroadcast()丰捷、getService()等函數(shù)的實(shí)現(xiàn)代碼坯墨。
現(xiàn)在還有一個(gè)問題是,既然我們?cè)诋?dāng)初獲取PendingIntent時(shí)瓢阴,已經(jīng)指定了日后激發(fā)時(shí)需要遞送的intent(或intent數(shù)組)畅蹂,那么為什么send()動(dòng)作里還有一個(gè)intent參數(shù)呢健无?它們的關(guān)系又是什么呢荣恐?我猜想,PendingIntent機(jī)制的設(shè)計(jì)者是希望給激發(fā)端一個(gè)修改“待激發(fā)的intent”的機(jī)會(huì)累贤。比如當(dāng)初我們獲取PendingIntent對(duì)象時(shí)叠穆,如果在flags里設(shè)置了FILL_IN_ACTION標(biāo)志位,那么就說明我們?cè)试S日后在某個(gè)激發(fā)點(diǎn)臼膏,用新的intent的mAction域值硼被,替換掉我們最初給的intent的mAction域值。如果一開始沒有設(shè)置FILL_IN_ACTION標(biāo)志位渗磅,而且在最初的intent里已經(jīng)有了非空的mAction域值的話嚷硫,那么即使在激發(fā)端又傳入了新intent检访,它也不可能修改用新intent的mAction域值替換舊intent的mAction域值。
細(xì)心的讀者一定記得仔掸,當(dāng)初獲取PendingIntent對(duì)象時(shí)脆贵,我們可是向AMS端傳遞了一個(gè)intent數(shù)組噢,雖然一般情況下這個(gè)數(shù)組里只有一個(gè)intent元素起暮,但有時(shí)候我們也是有可能一次性傳遞多個(gè)intent的卖氨。比如getActivities()函數(shù)就可以一次傳遞多個(gè)intent「号常可是現(xiàn)在激發(fā)動(dòng)作send()卻只能傳遞一個(gè)intent參數(shù)筒捺,這該如何處理呢?答案很簡單纸厉,所傳入的intent只能影響已有的intent數(shù)組的最后一個(gè)intent元素系吭。大家可以看看sendInner里allIntents[allIntents.length-1] = finalIntent;一句。
Ok残腌,intent說完了村斟,下面就該做具體的激發(fā)了。我們以簡單的INTENT_SENDER_BROADCAST型PendingIntentRecord來說明抛猫,此時(shí)的激發(fā)動(dòng)作就是發(fā)送一個(gè)廣播:

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

至于其他類型的PendingIntentRecord的激發(fā)動(dòng)作蟆盹,大家可以自行查閱代碼,它們的基本代碼格局都是差不多的闺金。

3 小結(jié)
本文是基于我早先的一點(diǎn)兒筆記整理而成的逾滥。當(dāng)時(shí)為了搞清楚PendingIntent的機(jī)理,也查閱了一些網(wǎng)上的相關(guān)文章败匹,只是都不大滿足我的要求寨昙,后來只好自己看代碼,終于得了些自己的淺見∠颇叮現(xiàn)在把我過去的一點(diǎn)兒認(rèn)識(shí)整理出來舔哪,希望能對(duì)學(xué)習(xí)PendingIntent的同學(xué)有點(diǎn)兒幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末槽棍,一起剝皮案震驚了整個(gè)濱河市捉蚤,隨后出現(xiàn)的幾起案子悼沿,更是在濱河造成了極大的恐慌周偎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莉炉,死亡現(xiàn)場離奇詭異豌拙,居然都是意外死亡陕悬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門按傅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捉超,“玉大人胧卤,你說我怎么就攤上這事∑丛溃” “怎么了灌侣?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長裂问。 經(jīng)常有香客問我侧啼,道長,這世上最難降的妖魔是什么堪簿? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任痊乾,我火速辦了婚禮,結(jié)果婚禮上椭更,老公的妹妹穿的比我還像新娘哪审。我一直安慰自己,他們只是感情好虑瀑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布湿滓。 她就那樣靜靜地躺著,像睡著了一般舌狗。 火紅的嫁衣襯著肌膚如雪叽奥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天痛侍,我揣著相機(jī)與錄音朝氓,去河邊找鬼。 笑死主届,一個(gè)胖子當(dāng)著我的面吹牛赵哲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播君丁,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼枫夺,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了绘闷?” 一聲冷哼從身側(cè)響起橡庞,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎簸喂,沒想到半個(gè)月后毙死,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體燎潮,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喻鳄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了确封。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片除呵。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡再菊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颜曾,到底是詐尸還是另有隱情纠拔,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布泛豪,位于F島的核電站稠诲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诡曙。R本人自食惡果不足惜臀叙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望价卤。 院中可真熱鬧劝萤,春花似錦、人聲如沸慎璧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胸私。三九已至厌处,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岁疼,已是汗流浹背嘱蛋。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留五续,地道東北人洒敏。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像疙驾,于是被迫代替她去往敵國和親凶伙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容