13.2.3 App Widget的事件处理和Pending Intent

App Widget对象的事件触发者和处理者也分属不同的应用,当用户与App Widget对象进行交互时,宿主应用需要将相关的交互事件回传到该App Widget提供的应用中进行处理。

在Android中,只可以通过RemoteViews.setOnClickPendingIntent函数为App Widget中的控件设置点击事件。当用户点击控件时,就会预设android.app.PendingIntent对象中的Intent信息,发出承载该交互事件的Intent对象:


RemoteViews views=new RemoteViews(getPackageName(),

R.layout.appwidget_layout);

//在搜索按钮上设置事件,点击后弹出搜索界面

Intent searchIntent=new Intent("com.sample.Search",null);

PendingIntent event=PendngIntent.getActivity(

Context,0,searchIntent,0);

views.setOnClickPendingIntent(R.id.search, event);


其中,PendingIntent是Android中用于异步触发事件的对象。当Intent对象的构造者与发送者不同时,就需要将Intent对象缓存在PendingIntent中,从构造者传递到发送者中,等待合适的时机进行触发。

PendingIntent中可以缓存任意类型的Intent对象,接收该Intent对象的组件可以是触发器组件、服务组件或是界面组件。在App Widget的开发中,开发者可以将所需的交互事件构造成不同的PendingIntent对象,绑定到远程界面控件RemoteViews的特定操作上,当用户在绑定了PendingIntent对象的界面控件上进行点击等操作时,系统调用PendingIntent.send函数构造并发出Intent对象请求。如果需要在点击后弹出新的交互界面,则可以调用PendingIntent.getActivity函数进行设定;如果需要变更App Widget本身的界面信息,则可以调用PendingIntent.getService函数来预设对相关服务组件的通知。

PendingIntent不仅用在处理App Widght的交互事件中,在所有需要异步触发事件的场合下,也会通过该方式进行事件的构造(比如,前面提到的短信发送结果通知,后面即将介绍的提醒事件处理,等等)。在Android系统中,所有的PendingIntent对象,都由组件管理服务统一调度,每个PendingIntent都由与之绑定的Intent对象唯一标识,比如,具有同样Action、同样Category、同样Data信息的PendingIntent(除了Extra信息,所有Intent中的相关信息都会用来唯一标识PendingIntent),会被视为同一PendingIntent,在组件管理服务中只保存一份。因此,在构造PendingIntent时,需要特别关注PendingIntent对象的生命周期,如果新构建的PendingIntent对象已经存在,开发者需要做出选择,是使用新构建的PendingIntent对象取代原有的,还是一旦发现已经存在PendingIntent对象了就不再构造新的。

在实际开发中,可以通过不同的标志位来控制PendingIntent的生命周期(标志位指的是调用PendingIntent.getActivity等构造函数时传入的flags参数—传入的flags参数不同,获取到的PendingIntent对象会有所不同,其行为也会有所不同),PendingIntent支持的标志位包括:

1)PendingIntent. FLAG_CANCEL_CURRENT。该标志位表示,如果系统中已经存在同样标识的PendingIntent对象,那么取消老的PendingIntent对象,用新构造的取代它。

2)PendingIntent. FLAG_UPDATE_CURRENT。该标志位表示,如果系统中已经存在同样标识的PendingIntent对象,那么就更新老的PendingIntent对象,它与PendingIntent.FLAG_CANCEL_CURRENT的区别在于,它并不在意前者是否已经被触发,如果是FLAG_CANCEL_CURRENT会取消触发但尚未通知的Intent对象,而FLAG_UPDATE_CURRENT不会。

3)PendingIntent. FLAG_ONE_SHOT。顾名思义,该标志位表示该Intent对象仅会被触发一次,一旦被触发,就自动取消不再触发。而在其他的标志位下,PendingIntent中封装的Intent消息,都是可以被反复触发的,直至PendingIntent.cancel函数被开发者主动调用。

4)PendingIntent. FLAG_NO_CREATE。该标志位表示,同样标识的PendingIntent对象,以首次构造的为准,一旦存在,后续同样标识的PendingIntent对象都无法构造。此时,调用PendingIntent.getActivity等函数,均会返回空值。

在开发中,需要根据具体的业务需求,仔细考虑标志位的使用。比如,如果App Widget的一个按钮的作用是“开始”,只能够单击一次,那么就可能需要将其标志位设定为PendingIntent.FLAG_ONE_SHOT;如果App Widget上某个标签的内容是不断更新的,单击后需要根据当前的内容执行不同的操作,那就需要将其设定为PendingIntent.FLAG_UPDATE_CURRENT或者PendingIntent.FLAG_CANCEL_CURRENT。

通过前面的介绍不难看出,PendingIntent的标识完全取决于Intent对象的内容,同一Intent对象只能有一个PendingIntent对象与之对应。但在有的场景下,开发者需要保存多个具有相同Intent对象信息的PendingIntent对象,比如,在实现短信群发功能时,发给不同联系人的短信Intent对象,除了存放在Extra域中的短信地址等信息会有所不同,所有的Action、Categary等信息都完全一致。为了能够同时保存多个群发给不同联系人的PendingIntent,开发者需要在构造PendingIntent对象时使用请求码(所谓请求码,就是调用PendingIntent.getActivity等构造函数时,传入的requestCode参数):


//构造发送短信的PendingIntent对象,每个对象用不同的请求码唯一标识

for(int i=0;i<sms_number;++i){

smsIntent=createSms(i);

PendingIntent sendSms=PendngIntent.getActivity(

Context,

i,//用短信索引值唯一标识该PendingIntent对象

smsIntent,

PendingIntent.FLAG_UPDATE_CURRENT);

}


通过示例中的方式来构造PendingIntent对象,会使的组件管理服务中构造多个具有同样Intent信息的PendingIntent对象,换句话说,组件管理服务,其实是通过Intent内容本身和请求码来唯一标识PendingIntent对象的(特别需要注意的是,在Android的SDK文档中,对请求码的描述有误。SDK文档称请求码是无用的,在系统内部并未使用,但从Android源代码和开发中的使用效果来看,请求码是实现同一Intent对象不同PendingIntent实例的有效方式,文档中的内容,应该是经年未改了)。

PendingIntent的构造与发送是异步的,因此,在构建请求界面组件的PendingIntent对象时,务必将请求界面组件的Intent对象标志位置为Intent.FLAG_ACTIVITY_NEW_TASK。如果不添加该参数,延迟发送的Intent请求无法记住其发送者是何组件,也就无法有效地将该对象添加到组件栈中。通过Intent.FLAG_ACTIIVTY_NEW_TASK参数,可以将其需要加入的组件栈清空,保持其根的一致性。

介绍了PendingIntent的相关内容,再继续回到App Widget的事件处理来,因为使用了PendingIntent作为事件交互的载体,使得App Widget不适合处理复杂的交互事件,所以在界面设计上,需要保持交互的简单性,保证可以通过点击完成所有的操作,就不要使用长按、滑动等交互策略[1]

注意 App Widget是Android为各个应用开放的“窗口”,通过App Widget,应用可以提供方便用户及时查看的信息以及更为便捷的操作入口。对于每个Android开发者而言,为应用构造合适的App Widget,是需要考虑的方向。

RemoteView和PendingIntent是Android实现异步界面构造和异步事件触发的核心机制,它们不仅应用在App Widget的实现中,也应用在Android的其他类似场景中。

[1]在Android 3.1以后的版本中,Android开始支持交互更为复杂的App Widget。