需求是這樣的滞项,一個(gè) Android 應(yīng)用要求彈出一個(gè)通知腾供,同時(shí)通知上還有兩個(gè)按鈕澳窑,通知本身點(diǎn)擊和按鈕點(diǎn)擊都對應(yīng)不同的響應(yīng)钠怯,但是在實(shí)現(xiàn)后發(fā)現(xiàn)不管點(diǎn)哪個(gè)按鈕最后都響應(yīng)同一個(gè)事件, 既返回的參數(shù)都是同一個(gè)恋追。
我的實(shí)現(xiàn)方式是這樣的辕翰,BroadcastReceiver 接收到通知后根據(jù) key 作出不同的響應(yīng):
val pendingIntent1 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
setClass(context, BroadcastReceiver::class.java)
putExtra(key, value1)
}, PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent2 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
setClass(context, BroadcastReceiver::class.java)
putExtra(key, value2)
}, PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent3 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
setClass(context, BroadcastReceiver::class.java)
putExtra(key, value3)
}, PendingIntent.FLAG_UPDATE_CURRENT)
一開始每個(gè)響應(yīng)PendingIntent都是全局變量嘁酿,只實(shí)例化一次璧瞬,我懷疑是因?yàn)楹罄m(xù)更新通知導(dǎo)致宁脊,所以改成每次更改通知時(shí)都新建 一個(gè) PendingIntent,結(jié)果還是不行撕彤。
后來我把每個(gè) PendingIntent 打印出來如下:
PendingIntent{2fd4457: android.os.BinderProxy@2f2cb44}
PendingIntent{5e7742d: android.os.BinderProxy@2f2cb44}
PendingIntent{c05d62: android.os.BinderProxy@2f2cb44}
而PendingIntent對象都toString() 方法是這樣的:
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("PendingIntent{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(": ");
sb.append(mTarget != null ? mTarget.asBinder() : null);
sb.append('}');
return sb.toString();
}
從上面兩張圖可以得出結(jié)論鱼鸠,雖然 PendingIntent 每次都是一個(gè)新的對象猛拴,但是他們都指向一個(gè) IIntentSender,吶泥蚀狰,why愉昆?
首先, 我們看看 PendingIntent 是如何創(chuàng)建的?
PendingIntent 提供幾個(gè)靜態(tài)方法來實(shí)例化自己:
PendingIntent.getActivity()
PendingIntent.getBroadcast()
PendingIntent.getService()
...
以 getBroadcast() 為例
public static PendingIntent getBroadcast(Context context, int requestCode,
Intent intent, @Flags int flags) {
return getBroadcastAsUser(context, requestCode, intent, flags, context.getUser());
}
/**
* @hide
* Note that UserHandle.CURRENT will be interpreted at the time the
* broadcast is sent, not when the pending intent is created.
*/
public static PendingIntent getBroadcastAsUser(Context context, int requestCode,
Intent intent, int flags, UserHandle userHandle) {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
try {
intent.prepareToLeaveProcess(context);
IIntentSender target =
ActivityManager.getService().getIntentSender(
ActivityManager.INTENT_SENDER_BROADCAST, packageName,
null, null, requestCode, new Intent[] { intent },
resolvedType != null ? new String[] { resolvedType } : null,
flags, null, userHandle.getIdentifier());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
而上面打印出來的不同的 PendingIntent 對應(yīng)同一個(gè) target 就是上面通過 ActivityManager.getService().getIntentSender(...) 創(chuàng)建的麻蹋,這里 ActivityManager.getService 返回的是 ActivityManagerService跛溉,我們?nèi)タ纯催@個(gè)方法:
@Override
public IIntentSender getIntentSender(int type,
String packageName, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes,
int flags, Bundle bOptions, int userId) {
//...刪掉部分不重要的代碼
synchronized(this) {
int callingUid = Binder.getCallingUid();
int origUserId = userId;
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
type == ActivityManager.INTENT_SENDER_BROADCAST,
ALLOW_NON_FULL, "getIntentSender", null);
if (origUserId == UserHandle.USER_CURRENT) {
// We don't want to evaluate this until the pending intent is
// actually executed. However, we do want to always do the
// security checking for it above.
userId = UserHandle.USER_CURRENT;
}
try {
if (callingUid != 0 && callingUid != SYSTEM_UID) {
final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callingUid));
if (!UserHandle.isSameApp(callingUid, uid)) {
String msg = "Permission Denial: getIntentSender() from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
+ ", (need uid=" + uid + ")"
+ " is not allowed to send as package " + packageName;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}
// ... 最終會(huì)調(diào)用和返回這個(gè)方法, 繼續(xù)往下看看
return getIntentSenderLocked(type, packageName, callingUid, userId,
token, resultWho, requestCode, intents, resolvedTypes, flags, bOptions);
} catch (RemoteException e) {
throw new SecurityException(e);
}
}
}
IIntentSender getIntentSenderLocked(int type, String packageName,
int callingUid, int userId, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
Bundle bOptions) {
// 刪掉部分不重要的代碼
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);
// 這里根據(jù)傳經(jīng)來的一些參數(shù)構(gòu)建來一個(gè) key, 這個(gè)里很關(guān)鍵
PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, activity,
resultWho, requestCode, intents, resolvedTypes, flags,
SafeActivityOptions.fromBundle(bOptions), userId);
WeakReference<PendingIntentRecord> ref;
ref = mIntentSenderRecords.get(key);
PendingIntentRecord rec = ref != null ? ref.get() : null;
if (rec != null) {
if (!cancelCurrent) {
if (updateCurrent) {
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 rec;
}
makeIntentSenderCanceledLocked(rec);
mIntentSenderRecords.remove(key);
}
if (noCreate) {
return rec;
}
rec = new PendingIntentRecord(this, key, callingUid);
mIntentSenderRecords.put(key, rec.ref);
if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
if (activity.pendingResults == null) {
activity.pendingResults
= new HashSet<WeakReference<PendingIntentRecord>>();
}
activity.pendingResults.add(rec.ref);
}
return rec;
}
上面的代碼中可以看到最終返回的是PendingIntentRecord對象(既target = PendingIntentRecord),它是 IIntentSender.IIntentSender 的子類扮授,調(diào)用者傳入的參數(shù)會(huì)構(gòu)建成一個(gè)對象 PendingIntentRecord.Key芳室,而 mIntentSenderRecords 則是一個(gè) HashMap 緩存所有的 PendingIntentRecord。
上面用新建的 key 去 mIntentSenderRecords 查詢是否已經(jīng)存在對應(yīng)的 PendingIntentRecord刹勃,如存在則直接返回堪侯,否則新建一個(gè) PendingIntentRecord 塞入 mIntentSenderRecords 并返回。
到這里稍微有一點(diǎn)豁然開朗荔仁,但是別急伍宦,還有一點(diǎn)點(diǎn)迷霧等待我們?nèi)荛_。
既然 HashMap 覺得 PendingIntentRecord 已經(jīng)存在乏梁,那肯定是 PendingIntentRecord.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 SafeActivityOptions options;
Intent[] allIntents;
String[] allResolvedTypes;
final int flags;
final int hashCode;
final int userId;
private static final int ODD_PRIME_NUMBER = 37;
Key(int _t, String _p, ActivityRecord _a, String _w,
int _r, Intent[] _i, String[] _it, int _f, SafeActivityOptions _o, int _userId) {
// 刪掉部分無用代碼
requestIntent = _i != null ? _i[_i.length-1] : null; // 最后一個(gè)Intent
requestResolvedType = _it != null ? _it[_it.length-1] : null;
// 著重看一下 hash 生成的部分
int hash = 23;
// _f 是在 PendingIntent.getBroadcast 時(shí)傳入的 flags
hash = (ODD_PRIME_NUMBER*hash) + _f;
// _r 是在 PendingIntent.getBroadcast 時(shí)傳入的 requestCode
hash = (ODD_PRIME_NUMBER*hash) + _r;
hash = (ODD_PRIME_NUMBER*hash) + _userId;
if (_w != null) {
hash = (ODD_PRIME_NUMBER*hash) + _w.hashCode();
}
if (_a != null) {
hash = (ODD_PRIME_NUMBER*hash) + _a.hashCode();
}
// requestIntent 就是你傳入的 Intent 數(shù)組里的最后一個(gè) Intent,
// 一般使用的時(shí)候只有一個(gè) Intent
// 這里為里生成 hashcode, 調(diào)用里 Intent.filterHashCode()
if (requestIntent != null) {
hash = (ODD_PRIME_NUMBER*hash) + requestIntent.filterHashCode();
}
if (requestResolvedType != null) {
hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode();
}
hash = (ODD_PRIME_NUMBER*hash) + (_p != null ? _p.hashCode() : 0);
hash = (ODD_PRIME_NUMBER*hash) + _t;
hashCode = hash;
}
public int hashCode() {
return hashCode;
}
}
接著看一下 Intent.filterHashCode 的生成算法
public int filterHashCode() {
int code = 0;
if (mAction != null) {
code += mAction.hashCode();
}
if (mData != null) {
code += mData.hashCode();
}
if (mType != null) {
code += mType.hashCode();
}
if (mPackage != null) {
code += mPackage.hashCode();
}
if (mComponent != null) {
code += mComponent.hashCode();
}
if (mCategories != null) {
code += mCategories.hashCode();
}
return code;
}
那我們捋一下 PendingIntentRecord.key 的 hashcode 和我們傳入的哪些參數(shù)有關(guān)系:
記得我們上面是如何創(chuàng)建 PendingIntent 的嗎掌呜?
val pendingIntent1 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
setClass(context, BroadcastReceiver::class.java)
putExtra(key, value1)
}, PendingIntent.FLAG_UPDATE_CURRENT)
我們傳入的參數(shù)是:
context, requestCode, Intent, flags
而 Intent 又包含哪些參數(shù)呢?
private String mAction;
private Uri mData;
private String mType;
private String mPackage;
private ComponentName mComponent;
private int mFlags;
private ArraySet<String> mCategories;
private Bundle mExtras;
private Rect mSourceBounds;
private Intent mSelector;
private ClipData mClipData;
private int mContentUserHint = UserHandle.USER_CURRENT;
private String mLaunchToken;
大概是上面這些, 其中參與了生成 hashcode 就是上面提到的 filterHashCode 這個(gè)方法里的參數(shù) 所以總共參與生成 hashcode 的和調(diào)用中有關(guān)的參數(shù)是:
context, requestCode, flags, Intent.action, Intent.data, Intent.package, Intent.component, Intent.categories
再回過頭來看我是如何創(chuàng)建 PendingIntent 的
val pendingIntent1 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
setClass(context, BroadcastReceiver::class.java)
putExtra(key, value1)
}, PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent2 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
setClass(context, BroadcastReceiver::class.java)
putExtra(key, value2)
}, PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent3 = PendingIntent.getBroadcast(context, 0, Intent(BROADCAST_ACTION).apply {
setClass(context, BroadcastReceiver::class.java)
putExtra(key, value3)
}, PendingIntent.FLAG_UPDATE_CURRENT)
?? 唯一不同的是 Intent.extras坪哄,卻沒有參與 hashcode 計(jì)算质蕉,不知道是 google 故意為止還是一個(gè)小 bug ??。
總結(jié):
- PendingIntent 中 的 target 是 PendingIntentRecord翩肌,它們都被緩存到 ActivityManagerService 中的一個(gè)HashMap中模暗,是弱引用類型;
- 影響 PendingIntent 復(fù)用的參數(shù)主要是: requestCode, flags, Intent.action, Intent.data, Intent.package, Intent.component, Intent.categories
所以在我的做法基礎(chǔ)上念祭,只要修改一下 requestCode 為不同的值就解決了兑宇。
參考:說說PendingIntent的內(nèi)部機(jī)制