`
zhiweiofli
  • 浏览: 511159 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Android开发陷阱:利用PendingIntent传递唯一的Intent

阅读更多

 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)

对比 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也是请求的唯一标志!

之后尝试一下的逻辑代码:

        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):



参考资料:

 

 

 

 

1
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics