RemoteViews的内部机制

2022-07-20 15:11:09 浏览数 (1)

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则只会更新界面。

0 人点赞