說說PendingIntent的內部機制
來源:程序員人生 發布時間:2015-01-06 08:52:25 閱讀次數:6473次
說說PendingIntent的內部機制
侯 亮
1 概述
在Android中,我們常常使用PendingIntent來表達1種“留待往后處理”的意思。從這個角度來講,PendingIntent可以被理解為1種特殊的異步處理機制。不過,單就命名而言,PendingIntent其實具有1定誤導性,由于它既不繼承于Intent,也不包括Intent,它的核心可以粗略地匯總成4個字――“異步激起”。
很明顯,這類異步激起常常是要跨進程履行的。比如說A進程作為發起端,它可以從系統“獲得”1個PendingIntent,然后A進程可以將PendingIntent對象通過binder機制“傳遞”給B進程,再由B進程在未來某個適合時機,“回調”PendingIntent對象的send()動作,完成激起。
在Android系統中,最合適做集中性管理的組件就是AMS(Activity Manager Service)啦,所以它當仁不讓地承當起管理所有PendingIntent的職責。這樣我們就能夠畫出以下示意圖:

注意其中的第4步“遞送相應的intent”。這1步遞送的intent是從何而來的呢?簡單地說,當發起端獲得PendingIntent時,實際上是需要同時提供若干intent的。這些intent和PendingIntent只是配套的關系,而不是聚合的關系,它們會被緩存在AMS中。往后,1旦處理端將PendingIntent的“激起”語義傳遞到AMS,AMS就會嘗試找到與這個PendingIntent對應的若干intent,并遞送出去。
固然,以上說的只是大概情況,實際的技術細節會更復雜1點兒。下面我們就來談談細節。
2 PendingIntent的技術細節
2.1 發起端獲得PendingIntent
我們先要理解,所謂的“發起端獲得PendingIntent”到底指的是甚么。難道只是簡單new1個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的動作了。
坦白說,這幾個函數的命名可真不怎樣樣,所以我們簡單解釋1下。上面的getActivity()的意思實際上是,獲得1個PendingIntent對象,而且該對象往后激起時所做的事情是啟動1個新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代理對應著同1個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(雖然1般情況下只會指定1個intent,但類似getActivities()這樣的函數還是可以指定多個intent的),而后者可以粗淺地理解為用戶所指定的那個intent數組中的最后1個intent?,F在大家應當清楚異步激起時用到的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⑴] : null; // intent數組中的最后1個
requestResolvedType = _it != null ? _it[_it.length⑴] : null;
allIntents = _i; // 所有intent
allResolvedTypes = _it;
flags = _f;
options = _o;
. . . . . .
}
Key不光承當著記錄信息的作用,它還承當“鍵值”的作用。
2.3 AMS中的PendingIntentRecord總表
在AMS中,管理著系統中所有的PendingIntentRecord節點,所以需要把這些節點組織成1張表:
final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>
mIntentSenderRecords
這張哈希映照表的鍵值類型就是剛才所說的PendingIntentRecord.Key。
以后每當我們要獲得PendingIntent對象時,PendingIntent里的mTarget是這樣得到的:AMS會先查mIntentSenderRecords表,如果能找到符合的PendingIntentRecord節點,則返回之。如果找不到,就創建1個新的PendingIntentRecord節點。由于PendingIntentRecord是個binder實體,所以經過binder機制傳遞后,客戶進程拿到的就是個合法的binder代理。如此1來,前文的示意圖可以進1步修改成下圖:

2.4 AMS里的getIntentSender()函數
現在,我們回過頭繼續說前文的getActivity(),和其調用的getIntentSender()。我們先列1遍getActivity()的原型:
public static PendingIntent getActivity(Context context, int requestCode,
Intent intent, int flags, Bundle options)
- context參數是調用方的上下文。
- requestCode是個簡單的整數,起辨別作用。
- intent是異步激起時將發出的intent。
- flags可以包括1些既有的標識,比如FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT等等。很多同學對這個域不是很清楚,我們后文會細說。
- options可以攜帶1些額外的數據。
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()要多1些,我們逐一說明。
type參數表明PendingIntent的類型。getActivity()和getActivities()動作里指定的類型值是INTENT_SENDER_ACTIVITY,getBroadcast()和getService()和動作里指定的類型值分別是INTENT_SENDER_BROADCAST和INTENT_SENDER_SERVICE。另外,在Activity.java文件中,我們還看到1個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參數息息相干,1般也是null啦。在createPendingResult()中,其值為Activity的mEmbeddedID字符串。
requestCode參數是個簡單的整數,可以在獲得PendingIntent時由用戶指定,它可以起辨別的作用。
intents數組參數是異步激起時希望發出的intent。對getActivity()、getBroadcast()和getService()來講,都只會指定1個intent而已。只有getActivities()會嘗試1次傳入若干intent。
resolvedTypes參數基本上和intent是相干的。1般是這樣得到的:
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
這個值常常和intent內部的mData URI有關系,比如終究的值多是URI對應的MIME類型。
flags參數可以指定PendingIntent的1些行動特點。它的取值是1些既有的比特標識的組合。目前可用的標識有: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參數可以攜帶1些額外數據。
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()函數中有1段逐條判斷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標識符?!癇OOT_UPGRADE”應當是“啟動并升級”的意思,它不能使用PendingIntent。
getIntentSender()中最核心的1句應當是調用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,如果找不到就創建1個新的節點。
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⑴] = 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,此時1概直接返回。
return rec;
}
// 從哈希映照表中找不到,而且又沒有寫明FLAG_NO_CREATE,此時創建1個新節點
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) 將傳進來的多個參數信息整理成1個PendingIntentRecord.Key對象(key);
2) 嘗試從mIntentSenderRecords總表中查找和key符合的PendingIntentRecord節點;
3) 根據flags參數所含有的意義,對得到的PendingIntentRecord進行加工。有時候修改之,有時候刪除之。
4) 如果在總表中沒有找到對應的PendingIntentRecord節點,或根據flags的語義刪除剛找到的節點,那末此時的默許行動是創建1個新的PendingIntentRecord節點,并插入總表。除非flags中明確指定了FLAG_NO_CREATE,此時不會創建新節點。
2.4.3 說說flags
從getIntentSenderLocked()的代碼中,我們終究弄明白了flags中那些特定比特值的意義了。我們現在總結1下。
應當說這些flags比特值基本上都是在圍繞著mIntentSenderRecords總表說事的。其中,FLAG_CANCEL_CURRENT的意思是,當我們獲得PendingIntent時,如果可以從總表中查到1個符合的已存在的PendingIntentRecord節點的話,那末需要把這個節點從總表中清算出去。而在沒有指定FLAG_CANCEL_CURRENT的大條件下,如果用戶指定了FLAG_UPDATE_CURRENT標識,那末會用新的intents參數替掉剛查到的PendingIntentRecord中的舊intents。
而不論是剛清算了已存在的PendingIntentRecord,還是壓根兒就沒有找到符合的PendingIntentRecord,只要用戶沒有明確指定FLAG_NO_CREATE標識,系統就會盡力創建1個新的PendingIntentRecord節點,并插入總表。
至于FLAG_ONE_SHOT標識嘛,它并沒有在getIntentSenderLocked()中露臉兒。它的名字是“FLAG_ONE_SHOT”,也就是“只打1槍”的意思,那末很明顯,這個標識起作用的地方應當是在“激起”函數里。在終究的激起函數(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;
}
意思很簡單,1進行激起就把相應的PendingIntentRecord節點從總表中清算出去,而且把PendingIntentRecord的canceled域設為true。這樣,以后即使外界再調用send()動作都沒用了,由于再也沒法進入if (!canceled)判斷了。
2.4.4 將PendingIntentRecord節點插入總表
接下來getIntentSenderLocked()函數new了1個PendingIntentRecord節點,并將之插入mIntentSenderRecords總表中。
2.5 PendingIntent的激起動作
下面我們來看PendingIntent的激起動作。在前文我們已說過,當需要激起PendingIntent之時,主要是通過調用PendingIntent的send()函數來完成激起動作的。PendingIntent提供了多個情勢的send()函數,但是這些函數的內部其實調用的是同1個send(),其函數原型以下:
public void send(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler, String requiredPermission)
throws CanceledException
該函數內部最關鍵的1句是:
int res = mTarget.send(code, intent, resolvedType,
onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null,
requiredPermission);
我們前文已介紹過這個mTarget域了,它對應著AMS中的某個PendingIntentRecord。
所以我們要看1下PendingIntentRecord1側的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。而后的操作和普通激起時的動作是1致的,也就是說也會走下面的第3)步。
3) 關于普通激起時應履行的邏輯動作是,根據當初創建PendingIntentRecord節點時,用戶指定的type類型,進行不同的處理。這個type其實就是我們前文所說的INTENT_SENDER_ACTIVITY、INTENT_SENDER_BROADCAST、INTENT_SENDER_SERVICE等類型啦,大家如有興趣,可自己參考本文1開始所說的getActivity()、getBroadcast()、getService()等函數的實現代碼。
現在還有1個問題是,既然我們在當初獲得PendingIntent時,已指定了往后激起時需要遞送的intent(或intent數組),那末為何send()動作里還有1個intent參數呢?它們的關系又是甚么呢?我料想,PendingIntent機制的設計者是希望給激起端1個修改“待激起的intent”的機會。比如當初我們獲得PendingIntent對象時,如果在flags里設置了FILL_IN_ACTION標志位,那末就說明我們允許往后在某個激起點,用新的intent的mAction域值,替換掉我們最初給的intent的mAction域值。如果1開始沒有設置FILL_IN_ACTION標志位,而且在最初的intent里已有了非空的mAction域值的話,那末即便在激起端又傳入了新intent,它也不可能修改用新intent的mAction域值替換舊intent的mAction域值。
仔細的讀者1定記得,當初獲得PendingIntent對象時,我們可是向AMS端傳遞了1個intent數組噢,雖然1般情況下這個數組里只有1個intent元素,但有時候我們也是有可能1次性傳遞多個intent的。比如getActivities()函數就能夠1次傳遞多個intent??墒乾F在激起動作send()卻只能傳遞1個intent參數,這該如何處理呢?答案很簡單,所傳入的intent只能影響已有的intent數組的最后1個intent元素。大家可以看看sendInner里allIntents[allIntents.length⑴]
= finalIntent;1句。
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈