RemoteViews的作用是在其他进程中显示并更新View的界面,主要用于通知栏和桌面小部件。
RemoteViews并不能支持所有的View类型,它所支持的所有类型如下所示:
Layout:FrameLayout、LinearLayout、RelativeLayout、GridLayout
View:AnalogClock、Button、Chronometer、IamgeView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub
RemoteViews由于跨进程,没有提供findViewById方法,而是提供了一系列set方法(大部分set方法是通过反射实现的)来完成,部分set方法如下所示:
通知栏和桌面小部件分别由NotificationManager和AppWidgetManager管理,而NotificationManager和AppWidgetManager通过Binder分别和SystemServer进程中的NotifitionManagerService以及AppWidgetService进行通信。由此可见,通知栏和桌面小部件中的布局文件实际上市在NotifitionManagerService和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进程中文成的。
RemoteViews内部提供了一个Action的概念,Action代表一个View操作,Action同样实现了Parcelable接口。系统首先将VIew操作封装带Action对象并将这些对象传输到远程进程,接着在远程进程中执行Action对象的具体操作。我们在应用中每调用一次set方法,RemoteViews中就会添加一个对应的Action对象,当我们通过NotificationM anager和AppWidgetManager来提交我们的更新时,这些Action对象就会传输到远程进程并在远程进程中依次执行。远程进程通过RemoteViews的apply方法进行View的更新操作,RemoteViews的apply方法内部则会去调用所有Action对象并调用它们的apply方法,具体的View更新操作游Action对象的apply方法来完成。大致过程 可参看下图:
源码分析:
分析一下setTextViewText方法,源码如下:
代码语言:javascript复制 public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
看一下setCharSequence方法的源码:
代码语言:javascript复制 public void setCharSequence(int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}
addAction方法的源码是这样的:
代码语言:javascript复制 private void addAction(Action a) {
if (hasLandscapeAndPortraitLayouts()) {
throw new RuntimeException("RemoteViews specifying separate landscape and portrait"
" layouts cannot be modified. Instead, fully configure the landscape and"
" portrait layouts individually before constructing the combined layout.");
}
if (mActions == null) {
mActions = new ArrayList<Action>();
}
mActions.add(a);
// update the memory usage stats
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
RemoteViews内部有一个ArrayList的mActions成员,每调用一次set方法,就会创建一个Action对象并加入到ArrayList中。
接下来看一下RemoteView的apply方法:
代码语言:javascript复制 public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result = inflateView(context, rvToApply, parent);
loadTransitionOverride(context, handler);
rvToApply.performApply(result, parent, handler);
return result;
}
由上面代码可看出,加载出RemoteViews的布局文件后通过performApply方法执行操作,performApply方法内部则是遍历mActions并执行每一个Action对象的apply方法。
通知栏和桌面小部件的工作过程和上面的描述过程是一致的,通过NotificationManager和AppWidgetManager更新界面,在其内部的确是通过RemoteView是的apply和reapply方法来加载或者更新界面的,apply和reapply的区别在于:apply会加载局部并更新界面,而reapply则只会更新界面。