PendingIntent 是對真實Intent的一種封裝載體,能夠用來在出發時,根據Intent 喚起目標組件,如 Activity,Service,BroadcastReceiver 等。 html
例如,通常的推廣行爲:接收後臺推送消息,並展現在通知欄上,當用戶點擊消息通知後,喚起指定的目標: android
Java代碼
- Intent intent = new Intent(action);
-
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
對於一次性行爲,上面的實現沒有問題,但對於持續性的操做,問題就來了。 app
什麼是持續性的操做?簡單的例子就是,想豆瓣音樂客戶端在通知欄上顯示的那種,我稱它做」遠程交互「。 dom
在通欄上的交互,大體的模型是: 源碼分析
做爲開發者,咱們只須要關注模型中的 Notification 和 BackService 便可。當發生用戶交互,通知欄上的通知視圖會觸發PendingIntent,並將其包含的Intent傳到BackService,而後BackService根據具體的邏輯,更新對應的Notification視圖,同時綁定新的PendingIntent,對應的代碼以下: 性能
Java代碼
- PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
爲了使得新的PendingIntent生效,咱們還特意設置Flag爲 PendingIntent.FLAG_UPDATE_CURRENT,ok,如今這一切都沒問題。 this
那咱們稍稍把問題在搞複雜一點,我但願PendingIntent中的Intent帶上參數,像這樣: spa
Java代碼
- Intent intent = new Intent(action);
- intent.putExtra("data", parcelable);
而後就用PendingIntent封裝,而後你再去點擊具體的通知-->觸發,並在代碼中試圖取回設置好的 data 時,你會發現取到的data有問題----點擊多於二次(或者點擊第 2+ 個通知)時,data的值保持不變(和第一個通知,點擊第一次取得的值一致)! htm
Why? 圖片
請留意:public static PendingIntent getService ( Context context, int requestCode, Intent intent, int flags)
對比 API Doc 的截圖
對於參數 flags 可能的取值有: FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT
通常性而言,咱們都會選擇 FLAG_UPDATE_CURRENT,直接更新當前存在的PendingIntent,以提升性能。對於FLAG_UPDATE_CURRENT 的意義解析,見下面一段Doc的原文:
寫道
Flag for use with getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int), and getService(Context, int, Intent, int): if the described PendingIntent already exists, then keep it but its replace its extra data with what is in this new Intent. This can be used if you are creating intents where only the extras change, and don't care that any entities that received your previous PendingIntent will be able to launch it with your new extras even if they are not explicitly given to it.
上面短文明確指出 keep it but its replace its extra data with what is in this new Intent ,這裏就是全文的關鍵點----PendingIntent的陷阱之在!!!
對於上文中的字面意思,若是判斷爲新Intent,則會更新對應的extra data,可是系統是如何斷定新Intent的?Object.equals?Intent.filterEquals!可是從源碼分析,filrerEquals 比較擁有一樣的Action,不同的data的 Intent 一定是返回false的,那問題還會出在哪呢?
很差意思,咱們還漏了一個參數:requestCode,可是doc上明寫着:currently not used。類比Activity.startActivityForResult(Content content, Class<?> cls, int resquestCode)得知,resquestCode也是請求的惟一標誌!
以後嘗試一下的邏輯代碼:
Java代碼
- Intent intent = new Intent(action);
- intent.putExtra("data", parcelable);
-
- PendingIntent pendingIntent = PendingIntent.getService(context, UUID.randomUUID().hashCode(),
- intent, PendingIntent.FLAG_UPDATE_CURRENT);
結果不言而喻......
其實從getService的源碼實現能夠看出一點端倪:
Java代碼
- public static PendingIntent getService(Context context, int requestCode,
- Intent intent, int flags) {
- 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_SERVICE, packageName,
- null, null, requestCode, new Intent[] { intent },
- resolvedType != null ? new String[] { resolvedType } : null,
- flags, null, UserHandle.myUserId());
- return target != null ? new PendingIntent(target) : null;
- } catch (RemoteException e) {
- }
- return null;
- }
PendingIntent其實也是對 IItentSender 的一個封裝,那就意味着,在更新 PendingIntent 時,系統比較的應該是 IIntentSender,從那一大串「構造參數」來看,requestCode也在其中,這關係就脫不了了。
最後,總結一句,Google 你這不是明擺着坑人嗎?請看最新的最早Doc(ps:本地的SDK版本是 4.2.2):
參考資料: