远程View,和远程Service类似。
表示的是一个View的结构,可以在其他进程中显示,由于它在其他进程中显示,为了能够更新它的界面,RemoteViews 提供了一组基础的操作用于跨进程更新它的界面。
使用场景:通知栏、桌面小部件。
通知栏:NotificationManager的notify方法实现。
桌面小部件:AppWidgetProvider来实现,本质是一个广播。
以上两者界面都运行在其他进程,确切来说是系统的SystemServer进程,为了跨进程更新界面,都会用到RemoteViews,有一系列set方法,且这些方法只是View全部方法的子集,另外RemoteViews中所支持的View类型也是有限的。
RemoteViews的内部机制
RemoteViews的作用是在其他进程中显示并更新View界面。
为了更好地理解它的内部机制,我们先来看一下它的主要功能,首先看一下构造方法
public RemoteViews(String packageName, int layoutId)
它接收两个参数,第一个是当前应用的包名,第二个参数是待加载的布局文件。
RemoteViews目前并不能支持所有的View类型,它支持的所有类型如下:
Layout:FrameLayout、LinearLayout、RelativeLayout、GridLayout
View:AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub
上面描述的是RemoteViews所支持的所有View类型,不支持它们的子类以及其他类型,更无法使用自定义View。
RemoteViews 没有提供findViewById方法,因此无法直接访问里面的View元素,必须通过RemoteViews所提供一系列set方法来完成。
RemoteViews的部分set方法事实上大部分的set方法是通过反射来完成。
下面描述以下RemoteViews的内部机制,由于RemoteViews主要用于通知栏和桌面小部件中,这里通过它们来分析RemoteViews的工作过程。
我们知道,通知栏和桌面小部件分别由NotificationManager和AppWidgetManager管理,而它们通过Binder分别和SystemServer进程中的NotificationManagerService和AppWidgetService进行通信。由此可见,通知栏和桌面小部件中的布局文件实际上是在NotificationManagerService和AppWidgetService中被加载的,而它们运行在系统的SystemServer中,这就和我们的进程构成了跨进程通信的场景。
首先RemoteViews会通过Binder传递到SystemServer进程,这是因为RemoteViews实现了Parcelable接口,因此可以跨进程传输,系统会根据RemoteViews中的包名等信息去得到该应用的资源。
接着系统会对View执行一些列界面更新任务,这些任务就是之前我们通过set方法来提交的。set方法对View所做的更新并不是立刻执行的,在RemoteViews内部会记录所有的更新操作,具体的执行时机要等到RemoteViews被加载以后才能执行,这样RemoteViews就可以在SystemServer进程中显示了,具体的更新操作也是在SystemServer进程中完成的。
从理论上来所,系统完全可以通过Binder去支持所有的View和View操作,但是这样做的话代价太大,因为View的方法太多了,另外就是大量的IPC操作会影响效率。
为了解决这个问题,系统提供了一个Action的概念,Action代表一个View的操作,Action同样实现了Parcelable接口。
系统首先将View操作封装到Action对象,并将这些Action对象跨进程传输到远程进程,接着远程进程执行Action对象中的具体操作。
在我们的应用中,每调用一次set方法,RemoteViews中就会添加一个对应的Action对象,当我么通过NotificationManager的notify方法和AppWidgetManager的updateAppWidget来提交我们的更新时,这些对象就会传输到远程进程,并在远程进程中依次执行。
远程进程通过RemoteViews的apply方法来进行View的更新操作,apply方法内部则会去遍历所有的Action对象并调用它们的apply方法,具体的View更新操作 是由Action对象的apply方法来完成的。apply会加载布局并更新界面,而reApply则只会更新界面
上述设计的好处是显而易见的,首先不需要定义大量的Binder接口,其次通过在远程进程中批量执行RemoteViews的修改操作从而避免了大量的IPC操作,这就提高了程序的性能。
以上就是RemoteViews内部机制的理论描述,源码角度分析可以从 RemoteViews#setTextViewText方法入手。不再展开分析。
注意
内部使用ReflectionAction表示的是一个反射动作,通过它对View的操作会以反射的反射调用。使用RelectionAction的set方法有setTextViewText、setBoolean、setLong、setDouble等。
除了ReflectionAction,还有其他Action,比如TextViewSizeAction、ViewPaddingAction、SetOnClickPendingIntent等。因为ReflectionAction只能进行一个参数的操作,所以别的不能复用。
关于单击事件,RemoteViews只支持发起PendingIntent,不支持onClickListener那种模式。我们需要注意setOnClickPendingIntent、setPendingIntentTemplate以及setOnclickFillInIntent它们之间的区别和联系。首先setOnclickPendingIntent用于给普通View设置单击事件,当不能给集合(ListView和StackView)中的View设置单击事件,因为开销较大,所以系统禁止了这种方法;其次如果要给ListView和StackView中的item添加单击事件,这必须将setPendingIntentTemplate和setOnclickFillInIntent组合使用才可以。
RemoteViews的意义
实际使用中,比如现在有两个应用,一个应用需要更新另一个应用中的某个界面。
可以选择AIDL去实现,但是对界面的更新比较频繁,这个时候就会有效率问题,同时AIDL接口就可能会变得复杂。这个时候如果采用RemoteViews来实现就没有这个问题了,当然RemoteViews也有缺点,那就是仅支持一些常见的View,对于自定义View也是不支持的。
注意
如果打算采用RemoteViews来实现两个应用之间的界面更新,那么还有一个问题,那就是布局文件的加载问题。假设同一个应用的多进程下布局文件的资源id是一致的,但是两个应用不可能刚好一致,这种情况我们可以使用资源文件名称的方式加载。
参考资料
感谢以下文章作者
《Android开发艺术探索》第5章 理解RemoteViews
网友评论