PendingIntent 是对真实Intent的一种封装载体,可以用来在出发时,根据Intent 唤起目标组件,如 Activity,Service,BroadcastReceiver 等。
例如,一般的推广行为:接收后台推送消息,并展示在通知栏上,当用户点击消息通知后,唤起指定的目标:
Intent intent = new Intent(action);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
对于一次性行为,上面的实现没有问题,但对于持续性的操作,问题就来了。
什么是持续性的操作?简单的例子就是,想豆瓣音乐客户端在通知栏上显示的那种,我称它作”远程交互“。
在通栏上的交互,大致的模型是:
作为开发者,我们只需要关注模型中的 Notification 和 BackService 即可。当发生用户交互,通知栏上的通知视图会触发PendingIntent,并将其包含的Intent传到BackService,然后BackService根据具体的逻辑,更新对应的Notification视图,同时绑定新的PendingIntent,对应的代码如下:
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
为了使得新的PendingIntent生效,我们还特地设置Flag为 PendingIntent.FLAG_UPDATE_CURRENT,ok,现在这一切都没问题。
那我们稍稍把问题在搞复杂一点,我希望PendingIntent中的Intent带上参数,像这样:
Intent intent = new Intent(action);
intent.putExtra("data", parcelable);
然后就用PendingIntent封装,然后你再去点击具体的通知-->触发,并在代码中试图取回设置好的 data 时,你会发现取到的data有问题----点击多于二次(或者点击第 2+ 个通知)时,data的值保持不变(和第一个通知,点击第一次取得的值一致)!
Why?
请留意:public static PendingIntent getService ( Context context, int requestCode, Intent intent, int flags)
对于参数 flags 可能的取值有: FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT
一般性而言,我们都会选择 FLAG_UPDATE_CURRENT,直接更新当前存在的PendingIntent,以提高性能。对于FLAG_UPDATE_CURRENT 的意义解析,见下面一段Doc的原文:
上面短文明确指出 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也是请求的唯一标志!
之后尝试一下的逻辑代码:
Intent intent = new Intent(action);
intent.putExtra("data", parcelable);
PendingIntent pendingIntent = PendingIntent.getService(context, UUID.randomUUID().hashCode(),
intent, PendingIntent.FLAG_UPDATE_CURRENT);
结果不言而喻......
其实从getService的源码实现可以看出一点端倪:
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):
参考资料:
- http://stackoverflow.com/questions/4340431/how-can-i-correctly-pass-unique-extras-to-a-pending-intent
- http://stackoverflow.com/questions/4689029/send-extra-data-via-pendingintent-problem
- http://stackoverflow.com/questions/10537006/intent-extras-are-duplicated-when-using-flag-update-current-in-pendingintent-whe