RemoteViews支持在其他进程显示和更新View,由于RemoteViews没有findViewById的方法,因为它是远程的View,即使有findViewById我们也不知道远程app的资源文件id 所以如果想要更新View的内容 就要使用RemoteViews提供的一系列set方法:
RemoteViews每调用用一个set方法都会添加一个Action到Action列表中,Action也是序列化的,也可以通过Binder传到远程。当执行Notification的notify方法后,就会调用RemoteView的apply方法,该方法会遍历每个Action,执行Action的 apply函数,apply函数会通过反射的方式调用RemoteViews的子子view的method,并进行设置,从而完成布局更新。
进程的通信一般基于Binder机制,在使用RemoteViews的时候将RemoteViews传给远程进程,所以RemoteViews是可以序列化的,系统会根据RemoteViews的包名,去创建Inflater类,并根据layoutId创建出view,然后应用apply方法来更新布局的内容。理论上Binder可以支持View的所有操作,但是太麻烦,需要提供更多的IPC操作,降低IPC的效率。所以Android系统在实现界面更新的时候没有每调用一次set操作就进行一次IPC操作,而是在调用诸如NotificationManager的notify的时候,进行批量更新。例如,如下代码创建一个RemoteViews并设置TextView。
在调用setTextViewText的时候,RemoteViews的方法如下:
上面代码将setTextViewText转换成一个ReflectionAction,包含了textview_id、setText方法、value值。当调用appWidgetManager.updateAppWidget的时候就将remoteViews提交给远程进程,之后远程进程会执行RemoteViews的apply方法,如下所示:
RemoteViews的apply方法分为以下几步:
1、获取RemoteViews对象
2、获取Inflater布局加载服务
3、根据RemoteViews中的layoutId,加载布局
执行RemoteViews的performApply,执行Action列表的所有apply操作,进行View更新
参数root也就是RemoteViews的layoutId对应的父view,viewId也就是要查找的子view,这里就是id为text_1的TextView,Action的apply方法首先find出TextView,然后反射调用他的setText方法,进行value设置。
RemoteViews的apply和reapply的区别,apply多了一个创建布局的步骤,即创建Inflater后再inflate布局,之后再加载内容;而replay只会更新布局,不会创建新的布局。