RemoteViews的应用

RemoteViews在实际开发中,主要用在通知栏和桌面小部件的开发过程中。通知栏每 个人都不陌生,主要是通过NotificationManager的notify方法来实现的,它除了默认效果外,还可以另外定义布局。桌面小部件则是通过AppWidgetProvider来实现的,AppWidget-Provider本质上是一个广播。通知栏和桌面小部件的开发过程中都会用到 RemoteViews,它们在更新界面时无法像在Activity里面那样去直接更新View,这是因为二者的界面都运行在其他进程中,确切来说是系统的SystemServer进程。为了跨进程更新 界面,RemoteViews提供了一系列set方法,并且这些方法只是View全部方法的子集,另外 RemoteViews中所支持的View类型也是有限的,这一点会在5.2节中进行详细说明。下面 简单介绍一下RemoteViews在通知栏和桌面小部件中的使用方法,至于它们更详细的使用 方法请读者阅读相关资料即可,本章的重点是分析RemoteViews的内部机制。

5.1.1 RemoteViews在通知栏上的应用

参考文章

5.1.2 RemoteViews在桌面小部件上的应用

AppWidgetProvider是Android中提供的用于实现桌面小部件的类,其本质是一个广 播,即BroadcastReceiver,图5-2所示的是它的类继承关系

Android 桌面小部件需要的权限 android桌面小部件开发_set方法

  1. 定义小部件界面
  2. 定义小部件配置信息
  3. 定义小部件的实现类
  4. 在AndroidManifest.xml中声明小部件
    AppWidgetProvider除了最常用的onUpdate方法,还有其他几个方法:onEnabled、 onDisabled、onDeleted以及onReceive。这些方法会自动地被onReceive方法在合适的时间 调用。确切来说,当广播到来以后,AppWidgetProvider会自动根据广播的Action通过 onReceive方法来自动分发广播,也就是调用上述几个方法。这几个方法的调用时机如下
    所示。
    onEnable: 当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第 一次调用。
    onUpdate :小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的 更新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次。
    onDeleted: 每删除一次桌面小部件就调用一次。
    onDisabled: 当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一 个。
    onReceive: 这是广播的内置方法,用于分发具体的事件给其他方法。
    关于AppWidgetProvider的onReceive方法的具体分发过程,可以参看源码中的实现, 如下所示。通过下面的代码可以看出,onReceive中会根据不同的Action来分别调用 onEnable、onDisable和onUpdate等方法。

5.1.3 PendingIntent概述

PendingIntent表示一种处于pending状态的意图,而pending状态表示的是 一种待定、等待、即将发生的意思,就是说接下来有一个Intent(即意图)将在某个待定 的时刻发生。可以看出PendingIntent和Intent的区别在于,PendingIntent是在将来的某个不 确定的时刻发生,而Intent是立刻发生。PendingIntent典型的使用场景是给RemoteViews添 加单击事件,因为RemoteViews运行在远程进程中,因此RemoteViews不同于普通的 View,所以无法直接向View那样通过setOnClickListener方法来设置单击事件。要想给 RemoteViews设置单击事件,就必须使用PendingIntent,PendingIntent通过send和cancel方 法来发送和取消特定的待定Intent。

PendingIntent支持三种待定意图:启动Activity、启动Service和发送广播,对应着它的 三个接口方法。

Android 桌面小部件需要的权限 android桌面小部件开发_set方法_02


第二个参数requestCode和第四个参数 flags,其中requestCode表示PendingIntent发送方的请求码,多数情况下设为0即可,另外 requestCode会影响到flags的效果。flags常见的类型有:FLAG_ONE_SHOT、 FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT。

PendingIntent的匹配规则为:如果两个PendingIntent它们内部的Intent相同并且 requestCode也相同,那么这两个PendingIntent就是相同的。requestCode相同比较好理解, 那么什么情况下Intent相同呢?Intent的匹配规则是:如果两个Intent的ComponentName和 intent-filter都相同,那么这两个Intent就是相同的。需要注意的是Extras不参与Intent的匹配 过程,只要Intent之间的ComponentName和intent-filter相同,即使它们的Extras不同,那么 这两个Intent也是相同的。了解了PendingIntent的匹配规则后,就可以进一步理解flags参数 的含义了,如下所示。

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 是常量,那么多次调用notify只能弹出一个通知,后续的通知会把前面的通知完全替代 掉,而如果每次id都不同,那么多次调用notify会弹出多个通知,下面一一说明。

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

5.2 RemoteViews的内部机制

我们知道,通知栏和桌面小 部件分别由NotificationManager和AppWidgetManager管理,而NotificationManager和 AppWidgetManager通过Binder分别和SystemServer进程中的NotificationManagerService以及 AppWidgetService进行通信。由此可见,通知栏和桌面小部件中的布局文件实际上是在 NotificationManagerService以及AppWidgetService中被加载的,而它们运行在系统的 SystemServer中,这就和我们的进程构成了跨进程通信的场景。

首先RemoteViews会通过Binder传递到SystemServer进程,这是因为RemoteViews实现 了Parcelable接口,因此它可以跨进程传输,系统会根据RemoteViews中的包名等信息去得 到该应用的资源。然后会通过LayoutInflater去加载RemoteViews中的布局文件。在 SystemServer进程中加载后的布局文件是一个普通的View,只不过相对于我们的进程它是 一个RemoteViews而已。接着系统会对View执行一系列界面更新任务,这些任务就是之前 我们通过set方法来提交的。set方法对View所做的更新并不是立刻执行的,在 RemoteViews内部会记录所有的更新操作,具体的执行时机要等到RemoteViews被加载以

后才能执行,这样RemoteViews就可以在SystemServer进程中显示了,这就是我们所看到 的通知栏消息或者桌面小部件。当需要更新RemoteViews时,我们需要调用一系列set方法 并通过NotificationManager和AppWidgetManager来提交更新任务,具体的更新操作也是在 SystemServer进程中完成的。

Android 桌面小部件需要的权限 android桌面小部件开发_ide_03