Android中RemoteViews的解析

定义

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

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值