一、PendingIntent
在Android系統中,Intent經常被用來跨組建和跨進程通信,但是Intent只能被即時使用,爲此,Android系統中引入了PendingIntent,它本身並不是Intent,而是用來暫時性緩存Intent,等到相關Action觸發,在發送Intent。事實上,PendingIntent也可以通過send方法觸發,當然也是可以跨進程的,參考如下代碼。
Intent ia = new Intent(MainActivity.this,TestBroadcastReciever.class);
ia.putExtra("args1","aaaaaa");
PendingIntent pa = PendingIntent.getBroadcast(MainActivity.this,0,ia,PendingIntent.FLAG_UPDATE_CURRENT);
try {
pa.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
目前爲止只提供FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT這四個flag
FLAG_ONE_SHOT
獲取的PendingIntent只能使用一次,即使再次利用上面三個方法重新獲取,再使用PendingIntent也將失敗。
FLAG_NO_CREATE
從PendingIntentRecord緩存中獲取因存在的PendingIntent,若描述的Intent不存在則返回NULL值.
FLAG_UPDATE_CURRENT
如果描述的Intent存在,想繼續持有他但是需要修改部分數據。這就可以被使用如果你創建了一個新的Intent當你只想修改部分數據,而且不想關注他那些以前就存在的Intent會收到新的更新數據甚至不是特別的給它的。
二、PendingIntentRecord
我們從PendingIntent相關flag得知,PendingIntent實際上被緩存在了AMS中,而緩存PendingIntent的類爲PendingIntentRecord.java,其中key值包含如下數據
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;
String[] allResolvedTypes;
final int flags;
final int hashCode;
final int userId;
// 次數省略一些代碼
}
通過Key值我們才能在AMS中查詢到之前設置的PendingIntent。
三、PendingIntentRecord更新問題
通過flag我們發現能獲取PendingIntent緩存,那麼是如何查找的呢?只能看Key類中的全部代碼
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;
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, Bundle _o, int _userId) {
type = _t;
packageName = _p;
activity = _a;
who = _w;
requestCode = _r;
requestIntent = _i != null ? _i[_i.length-1] : null;
requestResolvedType = _it != null ? _it[_it.length-1] : null;
allIntents = _i;
allResolvedTypes = _it;
flags = _f;
options = _o;
userId = _userId;
int hash = 23;
hash = (ODD_PRIME_NUMBER*hash) + _f;
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();
}
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;
//Slog.i(ActivityManagerService.TAG, this + " hashCode=0x"
// + Integer.toHexString(hashCode));
}
public boolean equals(Object otherObj) {
if (otherObj == null) {
return false;
}
try {
Key other = (Key)otherObj;
if (type != other.type) {
return false;
}
if (userId != other.userId){
return false;
}
if (!Objects.equals(packageName, other.packageName)) {
return false;
}
if (activity != other.activity) {
return false;
}
if (!Objects.equals(who, other.who)) {
return false;
}
if (requestCode != other.requestCode) {
return false;
}
if (requestIntent != other.requestIntent) {
if (requestIntent != null) {
if (!requestIntent.filterEquals(other.requestIntent)) {
return false;
}
} else if (other.requestIntent != null) {
return false;
}
}
if (!Objects.equals(requestResolvedType, other.requestResolvedType)) {
return false;
}
if (flags != other.flags) {
return false;
}
return true;
} catch (ClassCastException e) {
}
return false;
}
public int hashCode() {
return hashCode;
}
public String toString() {
return "Key{" + typeName() + " pkg=" + packageName
+ " intent="
+ (requestIntent != null
? requestIntent.toShortString(false, true, false, false) : "<null>")
+ " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}";
}
String typeName() {
switch (type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
return "startActivity";
case ActivityManager.INTENT_SENDER_BROADCAST:
return "broadcastIntent";
case ActivityManager.INTENT_SENDER_SERVICE:
return "startService";
case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:
return "startForegroundService";
case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
return "activityResult";
}
return Integer.toString(type);
}
}
我們重點查看equals方法。
通過比較發現,requestCode和和flag是最容易影響查詢結果的參數。
我們還注意到,requestIntent的比較
public boolean filterEquals(Intent other) {
if (other == null) {
return false;
}
if (!Objects.equals(this.mAction, other.mAction)) return false;
if (!Objects.equals(this.mData, other.mData)) return false;
if (!Objects.equals(this.mType, other.mType)) return false;
if (!Objects.equals(this.mPackage, other.mPackage)) return false;
if (!Objects.equals(this.mComponent, other.mComponent)) return false;
if (!Objects.equals(this.mCategories, other.mCategories)) return false;
return true;
}
那麼問題來了,如何更新Intent中的數據呢?
四、方案
假設我們的PendingIntent如下
Intent ia = new Intent(MainActivity.this,TestBroadcastReciever.class);
ia.putExtra("args1","aaaaaa");
PendingIntent pa = PendingIntent.getBroadcast(MainActivity.this,0,ia,PendingIntent.FLAG_UPDATE_CURRENT);
如果Intent只包含一般參數,那麼PendingIntent將每次獲取到同樣的Record,發送給廣播的參數同樣是 “aaaaaa”
Intent ia = new Intent(MainActivity.this,TestBroadcastReciever.class);
ia.putExtra("args1","aaaaaa");
PendingIntent pa = PendingIntent.getBroadcast(MainActivity.this,0,ia,PendingIntent.FLAG_UPDATE_CURRENT);
Intent ib = new Intent(MainActivity.this,TestBroadcastReciever.class);
ib.putExtra("args1","bbbbbb");
PendingIntent pb = PendingIntent.getBroadcast(MainActivity.this,0,ib,PendingIntent.FLAG_UPDATE_CURRENT);
Log.d("PendingIntent","result :"+pb.equals(pa));
日誌輸出結果爲true
那麼我們想更新參數爲bbbbbb或者cccccc如何做呢?
方案一:
查詢之後cancel
Intent ib = new Intent(MainActivity.this,TestBroadcastReciever.class);
PendingIntent pb = PendingIntent.getBroadcast(MainActivity.this,0,ib,PendingIntent.FLAG_NO_CREATE);
if(pb!=null){
pb.cancel();
}
Intent ic = new Intent(MainActivity.this,TestBroadcastReciever.class);
ic.putExtra("args1","bbbbbb");
PendingIntent pc = PendingIntent.getBroadcast(MainActivity.this,0,ic,PendingIntent.FLAG_UPDATE_CURRENT);
Log.d("PendingIntent","result :"+pc.equals(pa));
缺陷:
如果在AlarmManager或者通知中設置了PendingIntent,cancel可能導致通知和alarmManager中的行爲失效。
方案二:
構建具備唯一性的PengingIntent,我們可以在Intent的data中加入時間戳或者url等,或者每次變更requestcode,保證使用最新數據
由於Key中以及Intent具備很多可以設置的數據,因此這種方式最簡單。
缺陷:這種本質上不屬於更新,但具備唯一性,因此通過新老數據版本校驗也可以更新最終傳遞的Intent
綜上:具體使用哪一種,根據需求決定