一、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
综上:具体使用哪一种,根据需求决定