RemoteViews的解析

来源:互联网 发布:js toggleclass 编辑:程序博客网 时间:2024/06/08 07:09

定义

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的操作调用:

@Overridepublic 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的使用演示跨进程更新界面的使用。

 

 

 

 

 

 

0 0
原创粉丝点击