android开发艺术(三)之RemoteViews

1.RemoteViews的应用

主要用于通知栏和桌面小部件的开发过程。通知栏和桌面小部件的开发过程中都会用到RemoteViews,它们在更新界面时无法像在Activity里面那样去直接更新View,这是因为二者的界面都运行在其他进程中,确切来说是系统的SystemServer 进程。为了跨进程更新界面,RemoteViews提供了一系列set方法,并且这些方法只是View全部方法的子集,另外RemoteViews中所支持的View 类型也是有限的

1.1 在通知栏的应用

通知栏主要是通过NotificationManager 的notify 方法来实现的,它除了默认效果外,还可以另外定义布局。
在这里插入图片描述

1.2 在桌面小部件的应用

桌面小部件则是通过AppWidgetProvider来实现的,AppWidgetProvider本质上是一个广播。
开发步骤:

  1. 定义小部件页面:
    Res/layout下新建一个XML文件用来表示小部件的样子,命名为Widget.xml,名称和内容可以自定义
<LinearLayout xmlns :android= "http://schemas.android.com/apk/res/android"
	android:layout_ width="match_ pa rent"
	android:layout_ height= "match parent"
	android: orientation- "vertical" >
	<ImageView
		android: id=" @+1d/imageView1"
		android: layout_ width= "wrap content"
		android: layout_ height- "wrap_ content"
		android:src= "edrawable/icon1" />
</LinearTavout>
  1. 定义小部件配置信息
    Res/xml下新建new_app_widget_info.xml 名称随意
<?xml version-"1.0" encodingr "utf-8"?>
<appwidget-provider xmlns : android="http://schemas.android.com/apk/res/android"
	android: initiallayout="elayout/widget"  小工具所使用的初始化布局
	android :minHeight="84dp"                定义小工具的最小尺寸
	android :minwidth="84dp"
	android: updatePeriodMillis="86400000"   定义小工具的自动更新周期.
</ appwidget provider>
  1. 定义小部件的实现类
    这个类需要继承AppWidgetProvider:public class MyAppWidgetProvider extends AppWidgetProvider
  2. 在AndroidManifest.xml中声明小部件
    最后一步,因为桌面小部件本质上是一个广播组件,因此必须要注册
<receiver android:name- . NewAppidget" >
	<intent-filter>
		<action android:name= android. appwidget. action. APPWIDCET UPDATE" />
	</intent-filter>
	<meta-data 
		android:name=" android. appwidget. provider
		android:resource=" @xml/new_app_widget_info" />
</receiver>

AppWidgetProvider除了最常用的onUpdate 方法,还有其他几个方法: onEnabled、onDisabled、onDeleted 以及onReceive.这些方法会自动地被onReceive方法在合适的时间调用。当广播到来以后,AppWidgetProvider 会自动根据广播的Action 通过onReceive方法来自动分发广播,也就是调用上述几个方法
onEnable() :当小部件第一次被添加到桌面时回调该方法,可添加多次,但只在第一次调用。对应广播的 Action 为 ACTION_APPWIDGET_ENABLE
onUpdate(): 当小部件被添加时或者每次小部件更新时都会调用一次该方法,配置文件中配置小部件的更新周期 updatePeriodMillis,每次更新都会调用。对应广播 Action 为:ACTION_APPWIDGET_UPDATE 和 ACTION_APPWIDGET_RESTORED
onDisabled(): 当最后一个该类型的小部件从桌面移除时调用,对应的广播的 Action 为 ACTION_APPWIDGET_DISABLED
onDeleted(): 每删除一个小部件就调用一次。对应的广播的 Action 为: ACTION_APPWIDGET_DELETED
onRestored(): 当小部件从备份中还原,或者恢复设置的时候,会调用,实际用的比较少。对应广播的 Action 为 ACTION_APPWIDGET_RESTORED
onAppWidgetOptionsChanged(): 当小部件布局发生更改的时候调用。对应广播的 Action 为 ACTION_APPWIDGET_OPTIONS_CHANGED

2.PendingIntent

PendingIntent 表示一种处于pending状态的意图,就是说接下来有一个Intent (即意图)将在某个待定的时刻发生。可以看出PendingIntent和Intent的区别在于,PendingIntent 是在将来的某个不确定的时刻发生,而Intent 是立刻发生。PendingIntent 典型的使用场景是给RemoteViews添加单击事件,因为RemoteViews 运行在远程进程中,因此RemoteViews 不同于普通的View,所以无法直接向View 那样通过setOnClickListener 方法来设置单击事件。要想给RemoteViews设置单击事件,就必须使用 PendingIntent 通过send和cancel方法来发送和取消特定的待定Intent。PendingIntent支持三种待定意图:启动Activity、启动Service 和发送广播,对应着它的三个接口方法
在这里插入图片描述
其中requestCode表示PendingIntent发送方的请求码,多数情况下设为0,requestCode会影响到flags的效果。flags 常见的类型有: FLAG ONE_ SHOT、FLAG_ NO_CREATE、FLAG CANCEL_CURRENT和FLAG_ UPDATE_ CURRENT

PendingIntent匹配规则:
PendingIntent的匹配规则为:如果两个PendingIntent 它们内部的Intent 相同并且requestCode也相同,那么这两个PendingIntent 就是相同的。Intent 的匹配规则是:如果两个Intent 的ComponentName
和intent-filter都相同,那么这两个Intent就是相同的。需要注意的是Extras不参与Intent的匹配过程,只要Intent 之间的ComponentName和intent-filter 相同,即使它们的Extras不同,那么这两个Intent 也是相同的。

  • FLAG_ ONE_SHOT
    当前描述的PendingIntent 只能被使用一次,然后它就会被自动cancel,如果后续还有相同的PendingIntent, 那么它们的send方法就会调用失败。对于通知栏消息来说,如果采用此标记位,那么同类的通知只能使用一次,后续的通知单击后将无法打开。
  • FLAG_NO_CREATE
    当前描述的PendingIntent 不会主动创建,如果当前PendingIntent 之前不存在,那么getActivity、getService 和getBroadcast方法会直接返回null,即获取PendingIntent失败。
  • FLAG_CANCEL_CURRENT
    当前描述的PendingIntent 如果已经存在,那么它们都会被cancel,然后系统会创建一个新的PendingIntent。 对于通知栏消息来说,那些被cancel的消息单击后将无法打开。
  • FLAG_ UPDATE_CURRENT
    当前描述的PendingIntent如果已经存在,那么它们都会被更新,即它们的Intent中的Extras会被替换成最新的。

举例说明:manager.notify( 1, notification),

  • 如果notify方法的id是常量,那么不管PendingIntent是否匹配,后面的通知会直接替换前面的通知
  • 如果notify 方法的id 每次都不同
    • 当PendingIntent 不匹配时,这里的匹配是指PendingIntent中的Intent相同并且requestCode相同,在这种情况下不管采用何种标记位,这些通知之间不会相互干扰。
    • 如果PendingIntent处于匹配状态时,这个时候要分情况讨论:
      • 如果采用了FLAG_ ONE_ SHOT标记位,那么后续通知中的PendingIntent会和第一条通知保持完全一致,包括其中的Extras,单击任何一条通知后, 剩下的通知均无法再打开,当所有的通知都被清除后,会再次重复这个过程;
      • 如果采用FLAG_ CANCEL_CURRENT标记位,那么只有最新的通知可以打开,之前弹出的所有通知均无法打开;
      • 如果采用FLAG_ UPDATE_CURRENT标记位,那么之前弹出的通知中的PendingIntent 会被更新,最终它们和最新的一条通知保持完全一致, 包括其中的Extras,并且这些通知都是可以打开的。

3.RemoteView的内部机制

构造方法: public RemoteViews(String packageName, int layoutId)
第一个参数是当前应用的包名,第二个参数表示待加载的布局文件,能够支持的View类型:

  • Layout:
    FrameLayout、LinearLayout、 RelativeLayout、 GridL ayout.
  • View:
    AnalogClock、Button. Chronometer. ImageButton. ImageView. ProgressBar. TextView.
    ViewFlipper. ListView. GridView. StackView. AdapterViewFlipper. ViewStub.

Set方法: 不能通过findViewById对RemoteViews进行访问,所以使用set完成
在这里插入图片描述
内部机制实现:
通知栏和桌面小部件分别是由NotificationManager和AppWidgetManager管理,这两者分别是通过Binder和SystemService进程中的NotificationManagerService和AppWidgetService进行通信,所以通知栏是在NotificationManagerService被加载的,这就和我们的进程构成了跨进程通信的场景
在这里插入图片描述
首先RemoteViews会通过Binder 传递到SystemServer 进程,RemnoteViews实现了Parcelable 接口,因此它可以跨进程传输,系统会根据RemoteViews中的包名等信息去得到该应用的资源。然后会通过Layoutlnflater去加载RemotevViews中的布局文件。在SystemServer进程中加载后的布局文件是一个 普通的View,相对于我们的进程它是一个RemoteViews。接着系统会对View执行一系列界面更新任务,这些任务是通过set 方法来提交的。set 方法对View 所做的更新并不是立刻执行的,在RemoteViews内部会记录所有的更新操作,具体的执行时机要等到RemoteViews被加载以后才能执行,这样RemoteViews就可以在SystemServer进程中显示。当需要更新RemoteViews时,我们需要调用一系列set 方法并通过NotificationManager和AppWidgetManager来提交更新任务,具体的更新操作也是在SystemServer进程中完成的。

Action 代表一个 View操作,Action 同样实现了Parcelable 接口。系统首先将View操作封装到Action对象并将这些对象跨进程传输到远程进程,接着在远程进程中执行Action 对象中的具体操作。在我们的应用中每调用一次set方法, RemoteViews中就会添加一个对应的Action对象,当我们通过NofificationManager和AppWidgetManager来提交我们的更新时,这些Action对象就会传输到远程进程并在远程进程中依次执行。远程进程通过RemoteViews的apply方法来进行View的更新操作,RemoteViews 的apply方法内部则会去遍历所有的Action对象并调用它们的apply方法,具体的View更新操作是由Action对象的apply方法来完成的。.上述做法不需要定义大量的Binder 接口,其次通过在远程进程中批量执行RemoteViews的修改操作从而避免了大量的IPC操作

AppWidgetManager的updateAppWidget 的内部实现中是通过RemoteViews的apply以及reapply 方法来加载或者更新界面的,apply 和reApply的区别在于: apply 会加载布局并更新界面,而reApply则只会更新界面。通知栏和桌面小插件在初始化界面时会调用apply 方法,而在后续的更新界面时则会调用reapply方法。

关于单击事件,RemoteViews 中只支持发起PendingIntent, 不支持onClickListener 那种模式。另外,我们需要注意setOnClickPendingIntent 、setPendingIntentTemplate 以及setOnClickillnIntent它们之间的区别和联系。首先setOnClickPendingIntent 用于给普通View设置单击事件,但是不能给集合(ListView 和StackView)中的View设置单击事件,比如我们不能给ListView中的item 通过setOnClickPendingIntent这种方式添加单击事件,因为开销比较大。其次,如果要给ListView和StackView中的item添加单击事件,则必须将setPendingIntentTemplate和setOnClickillnIntent组合使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值