介绍
远程View,它表示的是一个View结构,它可以在其他进程中显示,为了跨进程更新它的界面,RemoteViews提供了一组基础的操作来实现这个效果。
RemoteViews在Android中的使用场景有两种:
- 通知栏
- 桌面小部件
桌面小部件
- 定义小部件布局
- 定义小部件配置信息< appwidget-provider
属性 | 含义 |
---|---|
android:initialLayout | 指定小部件的初始化布局 |
android:minHeight | 小部件最小高度 |
android:minWidth | 小部件最小宽度 |
android:previewImage | 小部件列表显示的图标 |
android:updatePeriodMillis | 小部件自动更新的周期 |
android:widgetCategory | 小部件显示的位置,home_screen表示只在桌面上显示 |
- 定义小部件的实现类,extends AppWidgetProvider
- 清单文件声明
<receiver android:name=".CustomAppWidgetProvider">
<meta-data
android:name="android.appwidget.provider" // name固定
android:resource="@xml/app_widget_provider_info" /> // xml
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> // 必须加的,它作为小部件的标识存在
<action android:name="com.shenhuniurou.appwidgetprovider.click.one" /> // 其他action对应点击动作
<action android:name="com.shenhuniurou.appwidgetprovider.click.two" />
<action android:name="com.shenhuniurou.appwidgetprovider.click.three" />
<action android:name="com.shenhuniurou.appwidgetprovider.click.four" />
</intent-filter>
</receiver>
总结:
- 当小部件一被添加到桌面时会调用Provider中的onUpdate方法,在这个方法中我们会通过AppWidgetManager去更新小部件的界面
- 通过RemoteViews来操作,setOnClickPendingIntent给每个按钮设置了点击时会发送的广播动作,而在清单文件中我们声明小部件时已经将这些广播动作都加到intent-filter,所以当我们点击桌面上该小部件中的某个按钮时,就会发送对应的广播,而小部件监听了这个广播,接收到广播后再onReceive方法中根据动作来分别处理点击事件。
- 对小部件的一些其他操作方法(比如onEnabled、onDisabled、onDeleted)的广播也会在onReceive中接收到,然后分发给不同的方法。
PendingIntent
将要发生的意图,和Intent的区别就在于一个是立即执行的一个是在未来某个时候执行。(通知中点击通知时跳转页面)
给RemoteViews设置点击事件,就必须使用PendingIntent,通过setOnClickPendingIntent方法来设置。
PendingIntent是通过send和cancel方法来发送和取消待执行的Intent。
PendingIntent支持三种待定意图:
-
启动activity(常见的通知)
启动Activity它有两种,启动单个和启动多个,当使用getActivities时,实际上启动的是Intent数组中最后一个activity,如果要让最后一个activity返回时不退出app而是退回到上一个activity,实现方式可参照我上面第一个按钮的点击处理。 -
启动Service
-
发送广播
PendingIntent 方法参数
getActivity、getService、getBroadcast这三个方法的参数意义都是相同的,第一个上下文,第三个待定的意图,第二个requestCode表示PendingIntent发送方的请求码,多数情况下设置为0即可,另外requestCode会影响到第四个参数flags的效果。flags这个标志位表示执行效果。
PendingIntent的匹配规则
如果两个PendingIntent它们内部的Intent相同,且requestCode也相同,那么这两个PendingIntent就是相同的;
Intent相同的情况
如果两个Intent的ComponentName和intent-filter都相同,那么这两个Intent就是相同的
(Extras不参与Intent的匹配过程,就是它不同,只要ComponentName和intent-filter相同,Intent都算相同的。)
flags 执行效果
FLAG_ONE_SHOT:表示当前描述的PendingIntent只能被使用一次,然后它就会自动cancel,如果后续还有相同的PendingIntent,那么它们的send方法就会调用失败。如果通知栏消息使用这种标记位,同类型的通知就只会被打开一次,后续的通知将无法点开。
FLAG_NO_CREATE:表示当前描述的PendingIntent不会主动创建,如果当前PendingIntent之前不存在,那么getActivities等这些方法会直接返回null,获取PendingIntent失败。它无法单独使用。
FLAG_CANCEL_CURRENT:表示当前描述的PendingIntent如果已经存在,就cancel它,然后系统会创建一个新的。
FLAG_UPDATE_CURRENT:表示当前描述的PendingIntent如果已经存在,那么它会被更新,内部的Intent中的Extras也会被更新。
RemoteViews的内部机制
通知栏和桌面小部件分别由NotificationManager和AppWidgetManager来管理的,而NotificationManager和AppWidgetManager是通过Binder分别和SystemServer进程中的NotificationManagerService以及AppWidgetService进行通信。
因此,通知栏和桌面小部件中的布局文件实际上是在NotificationManagerService和AppWidgetService中被加载的,而他们运行在SystemServer中,这其实已经和我们自己的app进程构成了跨进程通信。
理论分析
- 首先RemoteViews会通过Binder传递到SystemServer进程
(因为RemoteViews实现了Parcelable接口,可以跨进程传输) - 系统会根据RemoteViews中的包名等信息去获取到该app的资源
- 然后通过LayoutInflater去加载RemoteViews中的布局文件。
在SystemServer进程中加载后的布局文件是一个普通的View,只不过对于我们的app进程来说,它是一个远程View也就是RemoteViews。 - 接着系统会对View执行一系列界面更新任务
这些任务就是之前我们通过set方法提交的,set方法对View的更新操作并不是立刻执行的
在RemoteViews内部会记录所有的更新操作,具体的执行要等到RemoteViews被完全加载以后,这样RemoteViews就可以在SystemServer中进程中显示了,这就是我们所看到的通知栏消息和桌面小部件。
当需要更新RemoteViews时,我们又需要调用一系列set方法通过NotificationManager和AppWidgetManager来提交更新任务,具体更新操作也是在SystemServer进程中完成的。
理论上讲系统完全可以通过Binder去支持所有的View和View操作,但是这样做代价太大,View的方法太多了,另外大量的IPC操作会影响效率。
为了解决这个问题,系统并没有通过Binder去直接支持View的跨进程访问,而是提供了一个Action的概念。
Action代表一个View操作,Action同样实现了Parcelable接口。系统首先将View操作封装到Action对象并将这些对象跨进程传输到远程进程,接着在远程进程中执行Action对象中的具体操作。
在我们的app中每调用一次set方法,RemoteViews中就会添加一个对应的Action对象,当我们通过NotificationManager和AppWidgetManager来提交我们的更新时,这些Action对象就会传输到远程进程并在远程进程中依次执行。
远程进程通过RemoteViews的apply方法来进行View的更新操作,apply方法内部是去遍历所有的Action对象并调用它们的apply方法,具体的View更新操作是由Action对象的apply方法来完成。
好处
- 不需要定义大量的Binder接口
- 通过在远程进程中批量执行RemoteViews的更新操作从而避免了大量的IPC操作,这就提高了程序的性能。
RemoteViews的优缺点
优点:实际开发中,跨进程通信我们可以选择AIDL去实现,但是如果对界面的更新比较频繁,这时会有效率问题,而且AIDL接口可能会变得很复杂,但如果采用RemoteViews来实现就没有这个问题了
缺点:仅支持一些常见的View,而对于自定义View是不支持的。
网友评论