定义
RemoteViews从字面翻译来看是远程的视图,其实是表示可以在其它进程中显示的View。RemoteViews在Android实际开发中,主要用在通知栏(可参考《Android中通知栏的使用》)和桌面小部件(可参考《Android中小部件的使用》。因为通知栏和小部件都运行在系统的SystemServer进程。所以要对它们进行界面的更新就必须使用RemoteViews来进行跨进程更新界面。
RemoteViews支持类型
RemoteViews并不是所有的View类型都支持,目前RemoteViews支持的类型:
Layout: FrameLayout、LineraLayout、RelativeLayout、GridLayout
View: AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub
注意:
RemoteViews不支持自定义View的他们的子类,如果使用其他的不支持的控件会抛 InflateException。
RemoteViews一系列set方法
RemoteViews没有提供findViewById方法,要访问View元素必须通过RemoteViews所提供的一系列set方法来完成,事实上大部分set方法是通过反射来完成的。常用部分方法如:
方法名 | 作用 |
setTextViewText | 设置TextView的文本 |
setTextViewSize | 设置TextView的字体大小 |
setTextColor | 设置TextView的字体颜色 |
setImageViewResource | 设置ImageView的图片资源 |
setImageViewResource | 设置ImageView的图片 |
setInt | 反射调用View对象的参数类型为int的方法 |
setLong | 反射调用View对象的参数类型为long的方法 |
setBoolean | 反射调用View对象的参数类型为boolean的方法 |
setOnClickPendingIntent | 为View添加单击事件,事件类型只能为PendingIntent |
内部机制
通知栏和桌面小部件分别由NotificationManager和AppWidgetManager管理,它们都是通过Binder分别像跨进程一样和SystemServer进程中的NotificationManager以及AppWidgetManager进行通信。所以说,通知栏和桌面小部件的View实际上是在SystemServer系统进程NotificationManager或AppWidgetManager中被加载的。
RemoteViews(实现了Parcelable接口)会通过Binder传递到SystemServer进程,系统会根据RemoteViews中的包名等信息去得到应用的资源。然后会通过LayoutInflater去加载RemoveViews中的布局文件。接着系统会对View执行一系列界面更新任务,这些任务就是之前提到通过set方法来提交的。set方法对View所做的更新并不是立刻执行,而是等到RemoteViews被加载以后才能执行,这样RemoteViews就可以在SystemServer进程中显示了。
原理解析
因为通过Binder去支持所有View和View操作代价太大,而且大量IPC操作会影响效率。所以系统提供了一个Action代表一个View操作,它实现了parcelable接口。在我们应用中每调用一次set方法,RemoteViews中就会添加一个对应的Action对象(系统将View操作封装到Action对象),当我们通过NotificationManager或AppWidgetManager来提交更新时,这些Action对象就会传输到远程进程SystemServer并依次进行。
远程进程通过RemoteViews的apply方法以及reapply方法来加载或更新界面的(apply会加载布局并更新界面,而reapply则只会更新界面),RemoteViews的apply方法和reapply方法内部则会去遍历所有Action对象并调用它们的apply方法。
重要源码解析
我们先来看看RemoteViews类的构造函数,构造函数接收包名和view资源id,为了后面系统根据这两个值来获取资源
/**
* Create a newRemoteViews object that will display the views contained
* in thespecified layout file.
*
* @parampackageName Name of the package that contains the layout resource
* @paramlayoutId The id of the layout resource
*/
public RemoteViews(String packageName, int layoutId){
mPackage =packageName;
mLayoutId =layoutId;
// setupthe memory usage statistics
mMemoryUsageCounter = new MemoryUsageCounter();
recalculateMemoryUsage();
}
接着我们拿setTextViewText方法来看,setTextViewText方法调用了setCharSequence方法,setCharSequence方法第二个参数是methodName,意思是方法名,值是:“setText”:
/**
* Equivalentto calling TextView.setText
*
* @paramviewId The id of the view whose text should change
* @param textThe new text for the view
*/
public void setTextViewText(int viewId, CharSequencetext) {
setCharSequence(viewId, "setText", text);
}
/**
* Call amethod taking one CharSequence on a view in the layout for this RemoteViews.
*
* @paramviewId The id of the view whose text should change
* @parammethodName The name of the method to call.
* @param valueThe value to pass to the method.
*/
public void setCharSequence(int viewId, StringmethodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName,ReflectionAction.CHAR_SEQUENCE, value));
}
setCharSequence方法调用了addAction方法,addAction方法接收一个内部抽象类Action的对象。可以看出每次调用setTextViewText方法就会创建一个Action对象然后用一个ArrayList保存起来:
/**
* Add anaction to be executed on the remote side when apply is called.
*
* @param a Theaction to add
*/
private void addAction(Action a) {
if(mActions == null) {
mActions = new ArrayList<Action>();
}
mActions.add(a);
// updatethe memory usage stats
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
接着,再看看RemoteViews的apply方法。它首先调用了inflateView方法,inflateView方法内部通过LayoutInflater加载RemoveViews的布局文件(通过getLayoutId方法获得构造函数传入的view资源id),加载完成后通过performApply方法去执行一些更新操作。
/** @hide */
public View apply(Context context, ViewGroup parent,OnClickHandler handler) {
RemoteViewsrvToApply = getRemoteViewsToApply(context);
View result= inflateView(context, rvToApply, parent);
loadTransitionOverride(context, handler);
rvToApply.performApply(result, parent, handler);
returnresult;
}
private View inflateView(Context context, RemoteViewsrv, ViewGroup parent) {
//RemoteViews may be built by an application installed in another
// user. Sobuild a context that loads resources from that user but
// stillreturns the current users userId so settings like data / time formats
// areloaded without requiring cross user persmissions.
finalContext contextForResources = getContextForResources(context);
ContextinflationContext = new ContextWrapper(context) {
@Override
publicResources getResources() {
return contextForResources.getResources();
}
@Override
publicResources.Theme getTheme() {
return contextForResources.getTheme();
}
@Override
publicString getPackageName() {
return contextForResources.getPackageName();
}
};
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// Cloneinflater so we load resources from correct context and
// we don'tadd a filter to the static version returned by getSystemService.
inflater =inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
View v =inflater.inflate(rv.getLayoutId(), parent, false);
v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
return v;
}
再来看看performApply方法的源码。它遍历mActions里的对象,然后执行Action对象的apply方法。所以Action对象的apply方法就是真正操作View的地方:
private void performApply(View v, ViewGroup parent,OnClickHandler handler) {
if(mActions != null) {
handler= handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
finalint count = mActions.size();
for(int i = 0; i < count; i++) {
Action a = mActions.get(i);
a.apply(v, parent, handler);
}
}
}
回头看下,我们之前addAction中传入的Action对象是内部类ReflectionAction,我们从它的apply方法可以清楚看到是通过反射来对View的操作调用:
@Override
public void apply(View root, ViewGroup rootParent,OnClickHandler handler) {
final Viewview = root.findViewById(viewId);
if (view ==null) return;
Class<?> param = getParameterType();
if (param== null) {
thrownew ActionException("bad type: " + this.type);
}
try {
getMethod(view, this.methodName, param).invoke(view,wrapArg(this.value));
} catch(ActionException e) {
throwe;
} catch(Exception ex) {
thrownew ActionException(ex);
}
}
好了,到这相信大家应该会对RemoteViews的工原理有了一定的理解了,我们之前介绍的通知栏和桌面小部件的使用中,RemoteViews只是当成参数传递给NotificationManager或AppWidgetManager,并没有使用到apply和reapply,下一节我们会对以实例方式来对RemoteViews的使用演示跨进程更新界面的使用。