PendingIntent

PendingIntent可以看作是对Intent的一个封装,但它不是立刻执行某个行为,

而是满足某些条件或触发某些事件后才执行指定的行为。

PendingIntent的获取

PendingIntent获取有三种方式:通过Activity,Service,BroadcastReceiver获取.

1. 你可以通过 getActivity(Context context, int requestCode, Intent intent, int flags) 系列方法从系统 取得一个用于启动一个Activity的PendingIntent对象.

2.可以通过 getService(Context context, int requestCode, Intent intent, int flags) 方法从系统取得一个 用于启动一个Service的PendingIntent对象.

3.可以通过 getBroadcast(Context context, int requestCode, Intent intent, int flags) 方法从系统取得一个用于向BroadcastReceiver的发送广播的PendingIntent对象.

PendingIntent的参数说明

拿第三种方式,广播的形式说明下

PendingIntent sentIntent = PendingIntent.getBroadcast(this, 0,sIntent, 0);

第一个参数是上下文.

第二个参数是每次requestcode不同,就能产生多个Pendingintent.

第三个参数是用来存储信息.

第四个参数是对不同操作作标识.

getBroadcast(Context context, int requestCode, Intent intent, int flags)中的flags有几种状态: 


1.FLAG_CANCEL_CURRENT:如果AlarmManager管理的PendingIntent已经存在,那么将会取消当前的PendingIntent,从而创建一个新的PendingIntent.

2.FLAG_UPDATE_CURRENT:如果AlarmManager管理的PendingIntent已经存在,让新的Intent更新之前Intent对象数据,
例如更新Intent中的Extras,另外,我们也可以在PendingIntent的原进程中调用PendingIntent的cancel ()把其从系统中移除掉

3.FLAG_NO_CREATE:如果AlarmManager管理的PendingIntent已经存在,那么将不进行任何操作,若描述的Intent不存直接返回NULL(空).

4.FLAG_ONE_SHOT:该PendingIntent只作用一次.在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话将会失败,系统将会返回一个SendIntentException.

Intent 是及时启动,intent 随所在的activity 消失而消失。 
a. Intent是立即使用的,而PendingIntent可以等到事件发生后触发,PendingIntent可以cancel
b. Intent在程序结束后即终止,而PendingIntent在程序结束后依然有效
c. PendingIntent自带Context,而Intent需要在某个Context内运行
d. Intent在原task中运行,PendingIntent在新的task中运行

 PendingIntent 是对真实Intent的一种封装载体,可以用来在出发时,根据Intent 唤起目标组件,如 Activity,Service,BroadcastReceiver 等。

例如,一般的推广行为:接收后台推送消息,并展示在通知栏上,当用户点击消息通知后,唤起指定的目标

1 Intent intent = new Intent(action);  
2 PendingIntent pendingIntent = PendingIntent.
3 getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 

对于一次性行为,上面的实现没有问题,但对于持续性的操作,问题就来了。

什么是持续性的操作?简单的例子就是,想豆瓣音乐客户端在通知栏上显示的那种,我称它作”远程交互“。

作为开发者,我们只需要关注模型中的 Notification 和 BackService 即可。当发生用户交互,通知栏上的通知视图会触发PendingIntent,并将其包含的Intent传到BackService,然后BackService根据具体的逻辑,更新对应的Notification视图,同时绑定新的PendingIntent,对应的代码如下:

Java代码  

PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 

PendingIntent.FLAG_UPDATE_CURRENT);  

为了使得新的 PendingIntent 生效,我们还特地设置 Flag 为 PendingIntent.FLAG_UPDATE_CURRENT,ok,现在这一切都没问题。

那我们稍稍把问题在搞复杂一点,我希望PendingIntent中的Intent带上参数,像这样:

Java代码  

1. Intent intent = new Intent(action);  
2. intent.putExtra("data", parcelable);  

然后就用PendingIntent封装,然后你再去点击具体的通知-->触发,并在代码中试图取回设置好的 data 时,你会发现取到的data有问题----点击多于二次(或者点击第 2+ 个通知)时,data的值保持不变(和第一个通知,点击第一次取得的值一致)!

Why?

一般性而言,我们都会选择 FLAG_UPDATE_CURRENT,直接更新当前存在的PendingIntent,以提高性能。对于FLAG_UPDATE_CURRENT  的意义解析,指出  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 也是请求的唯一标志!

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

1 Intent intent = new Intent(action);
2 
3  intent.putExtra("data", parcelable);  PendingIntent pendingIntent = 
4 
5 PendingIntent.getService(context, UUID.randomUUID().hashCode(),
6 
7                 intent, PendingIntent.FLAG_UPDATE_CURRENT);

 结果不言而喻......其实从getService的源码实现可以看出一点端倪:

Java代码  

1. public static PendingIntent getService(Context context, int requestCode,  

2.         Intent intent, int flags) {  

3.     String packageName = context.getPackageName();  

4.     String resolvedType = intent != null ? intent.resolveTypeIfNeeded(  

5.             context.getContentResolver()) : null;  

6.     try {  

7.         intent.setAllowFds(false);  

8.         IIntentSender target =  

9.             ActivityManagerNative.getDefault().getIntentSender(  

10.                 ActivityManager.INTENT_SENDER_SERVICE, packageName,  

11.                 null, null, requestCode, new Intent[] { intent },  

12.                 resolvedType != null ? new String[] { resolvedType } : null,  

13.                 flags, null, UserHandle.myUserId());  

14.         return target != null ? new PendingIntent(target) : null;  

15.     } catch (RemoteException e) {  

16.     }  

17.     return null;  

18. }  

      PendingIntent其实也是对 IItentSender 的一个封装,那就意味着,在更新 PendingIntent 时,系统比较的应该是 IIntentSender,从那一大串“构造参数”来看,requestCode也在其中,这关系就脱不了了。

Android的状态栏通知(Notification)

如果需要查看消息,可以拖动状态栏到屏幕下方即可查看消息。

步骤:

1获取通知管理器NotificationManager,它也是一个系统服务

2建立通知Notification notification = new Notification(icon, null, when);

3为新通知设置参数(比如声音,震动,灯光闪烁)

4把新通知添加到通知管理器

发送消息的代码如下:

//获取通知管理器

 1 NotificationManager mNotificationManager = 
 2 
 3 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)
 4 
 5 int icon = android.R.drawable.stat_notify_chat;
 6 
 7 long when = System.currentTimeMillis();//通知发生的时间为系统当前时间
 8 
 9 //新建一个通知,指定其图标和标题
10 
11 Notification notification = new Notification(icon, null, when);//第一个参数为图标,第二个参数为短暂提示标题,第三个为通知时间
12 
13 notification.defaults = Notification.DEFAULT_SOUND;//发出默认声音
14 
15 notification.flags |= Notification.FLAG_AUTO_CANCEL;//点击通知后自动清除通知
16 
17 Intent openintent = new Intent(this, OtherActivity.class);
18 
19 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, openintent, 0);//当点击消息时就会向系统发送openintent意图
20 
21 notification.setLatestEventInfo(this, “标题”, “我是内容", contentIntent);
22 
23 mNotificationManager.notify(0, notification);//第一个参数为自定义的通知唯一标识

重点是setLatestEventInfo( )方法的最后一个参数!!!!它是一个PendingIntent!!!!!!!!!

这里使用到了PendingIntent(pend本意是待定,不确定的意思)

PendingIntent可以看作是对Intent的包装。PendingIntent主要持有的信息是它所包装的Intent和当前Application的Context。正由于PendingIntent中保存有当前Application的Context,使它赋予带他程序一种执行的Intent的能力,就算在执行时当前Application已经不存在了,也能通过存在PendingIntent里的Context照样执行Intent。

PendingIntent的一个很好的例子:

SmsManager的用于发送短信的方法:

sendTextMessage(destinationAddress, scAddress, text, sentIntent, deliveryIntent);

第一个参数:destinationAddress对方手机号码

第二个参数:scAddress短信中心号码一般设置为空

第三个参数:text短信内容

第四个参数:sentIntent判断短信是否发送成功,如果你没有SIM卡,或者网络中断,则可以通过这个itent来判断。注意强调的是“发送”的动作是否成功。那么至于对于对方是否收到,另当别论

第五个参数:deliveryIntent当短信发送到收件人时,会收到这个deliveryIntent。即强调了“发送”后的结果

就是说是在"短信发送成功"和"对方收到此短信"才会激活 sentIntent和deliveryIntent这两个Intent。这也相当于是延迟执行了Intent


上面两个例子可以理解,PendingIntent就是一个可以在满足一定条件下执行的Intent,它相比于Intent的优势在于自己携带有Context对象,这样他就不必依赖于某个activity才可以存在。

短信系统举例代码

private final static String SEND_ACTION      = "send";private final static String DELIVERED_ACTION = "delivered";

private void sendSms(String receiver, String text) {

    SmsManager s = SmsManager.getDefault();

    PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, new Intent(SEND_ACTION),

                                                      PendingIntent.FLAG_CANCEL_CURRENT);

    PendingIntent deliveredPI = PendingIntent.getBroadcast(this, 0, new Intent(DELIVERED_ACTION),

                                                           PendingIntent.FLAG_CANCEL_CURRENT);

    // 发送完成

    registerReceiver(new BroadcastReceiver() {

        @Override

        public void onReceive(Context context, Intent intent) {

            switch (getResultCode()) {

                case Activity.RESULT_OK:

                    Toast.makeText(getBaseContext(), "Send Success!", Toast.LENGTH_SHORT).show();

                    break;

                case SmsManager.RESULT_ERROR_GENERIC_FAILURE:

                    Toast.makeText(getBaseContext(), "Send Failed because generic failure cause.",

                                   Toast.LENGTH_SHORT).show();

                    break;

                case SmsManager.RESULT_ERROR_NO_SERVICE:

                    Toast.makeText(getBaseContext(), "Send Failed because service is currently unavailable.",

                                   Toast.LENGTH_SHORT).show();

                    break;

                case SmsManager.RESULT_ERROR_NULL_PDU:

                    Toast.makeText(getBaseContext(), "Send Failed because no pdu provided.", Toast.LENGTH_SHORT).show();

                    break;

                case SmsManager.RESULT_ERROR_RADIO_OFF:

                    Toast.makeText(getBaseContext(), "Send Failed because radio was explicitly turned off.",

                                   Toast.LENGTH_SHORT).show();

                    break;

                default:

                    Toast.makeText(getBaseContext(), "Send Failed.", Toast.LENGTH_SHORT).show();

                    break;

            }

        }

    }, new IntentFilter(SEND_ACTION));

    // 对方接受完成

    registerReceiver(new BroadcastReceiver() {

        @Override

        public void onReceive(Context context, Intent intent) {

            switch (getResultCode()) {

                case Activity.RESULT_OK:

                    Toast.makeText(getBaseContext(), "Delivered Success!", Toast.LENGTH_SHORT).show();

                    break;

                default:

                    Toast.makeText(getBaseContext(), "Delivered Failed!", Toast.LENGTH_SHORT).show();

                    break;

            }

        }

    }, new IntentFilter(DELIVERED_ACTION));

    // 发送短信,sentPI和deliveredPI将分别在短信发送成功和对方接受成功时被广播

s.sendTextMessage(receiver, null, text, sentPI, delivere

I);

}

以上的两个PendingIntent sentPI和deliveredPI将分别在短信发送成功和对方接受成功时被广播

API以及一些重要方法

setResultExtras (Bundle extras)

这个函数是用来改变当前广播传来的Extra额外信息的;它只能通过Context.sendOrderedBroadcast.发送过来的广播有效;它使用Bundle来传递任意的数据,而这些数据只有接收器(broadcaster)才能解析。当然也可以把它设置为NULL,这样,它就把传来的数据映射全部清空了。

参数:

extras:新的数据映射,可以为空。

getResultExtras (boolean makeMap)得到额外的数据传入的参数时,只要不为空,那么 makeMap是否为 true 和 false 都能够得到数据。获得上一级传过来的setResultExtras(bundle);  里的数据;最后重新将bundle里的数据中添加数据。

参数:makemap没有好大的作用 

猜你喜欢

转载自www.cnblogs.com/endv/p/11576121.html