Android中的ViewRootImpl类源码解析

来源:互联网 发布:怀孕b超数据怎么看 编辑:程序博客网 时间:2024/05/20 05:25

转载请注明出处 http://blog.csdn.net/qianhaifeng2012/article/details/51737370

ViewRoot目前这个类已经没有了,是老版本中的一个类,在Android2.2以后用ViewRootImpl代替ViewRoot,对应于ViewRootImpl.java,他是链接WindowManager和DecorView的纽带,另外View的绘制也是通过ViewRootImpl来完成的。

它的主要作用我的总结为如下:

A:链接WindowManager和DecorView的纽带,更广一点可以说是Window和View之间的纽带。

B:完成View的绘制过程,包括measure、layout、draw过程。

C:向DecorView分发收到的用户发起的event事件,如按键,触屏等事件。

注:如果分析不对的地方,欢迎批评指正。


一、链接WindowManager和DecorView。

首先说第一个主要作用,链接WindowManager和DecorView,在ViewRootImpl.java中,开始的注释如下:

/** * The top of a view hierarchy, implementing the needed protocol between View * and the WindowManager.  This is for the most part an internal implementation * detail of {@link WindowManagerGlobal}. * * {@hide} */

通过这一段注释,我们知道,ViewRootImpl他是View树的树根,但它却又不是View,实现了View与WindowManager之间的通信协议,具体的实现详情在WindowManagerGlobal这个类中。

那么View与WindowManager之间是怎么建立联系的呢,WindowManager所提供的功能很简单,常用的只有三个方法,即添加View,更新View和删除View,当然还有其它功能哈,比如改变Window的位置,WindowManager操作Window的过程更像是在操作Window中的View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。

public interface ViewManager{    /**     * Assign the passed LayoutParams to the passed View and add the view to the window.     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming     * errors, such as adding a second view to a window without removing the first view.     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a     * secondary {@link Display} and the specified display can't be found     * (see {@link android.app.Presentation}).     * @param view The view to be added to this window.     * @param params The LayoutParams to assign to view.     */    public void addView(View view, ViewGroup.LayoutParams params);    public void updateViewLayout(View view, ViewGroup.LayoutParams params);    public void removeView(View view);}
Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window又通过ViewRootImpl与View建立联系,因此Window并不是实际存在的,他是以View的形式存在的。这点从WindowManager的定义也可以看出,它提供的三个接口方法addView,updateView,removeView都是针对View的,这说明View才是Window的实体,在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager。而对Window的访问(添加,更新,删除)都是通过ViewRootImpl实现的。这里以Window的添加过程为例,删除过程,更新过程就不再赘述了。

Window的添加过程

Window的添加过程需要通过WindowManager的addView来实现,WindowManager又是一个接口,它的实现类是WindowManagerImpl,在WindowManagerImpl中的三大操作如下:

    @Override    public void addView(View view, ViewGroup.LayoutParams params) {        mGlobal.addView(view, params, mDisplay, mParentWindow);    }    @Override    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {        mGlobal.updateViewLayout(view, params);    }    @Override    public void removeView(View view) {        mGlobal.removeView(view, false);    }    @Override    public void removeViewImmediate(View view) {        mGlobal.removeView(view, true);    }

可以看出,WindowManagerImpl又调用了WindowManagerGloble的三大操作方法,这正好说明了ViewRootImpl类上面一开始那个注释了。This is for the most part an internal implementation detail of {@link WindowManagerGlobal}.

addView方法源码如下:

    public void addView(View view, ViewGroup.LayoutParams params,            Display display, Window parentWindow) {        if (view == null) {            throw new IllegalArgumentException("view must not be null");        }        if (display == null) {            throw new IllegalArgumentException("display must not be null");        }        if (!(params instanceof WindowManager.LayoutParams)) {            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");        }        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;        if (parentWindow != null) {            parentWindow.adjustLayoutParamsForSubWindow(wparams);        } else {            // If there's no parent and we're running on L or above (or in the            // system context), assume we want hardware acceleration.            final Context context = view.getContext();            if (context != null                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;            }        }        ViewRootImpl root;        View panelParentView = null;        synchronized (mLock) {            // Start watching for system property changes.            if (mSystemPropertyUpdater == null) {                mSystemPropertyUpdater = new Runnable() {                    @Override public void run() {                        synchronized (mLock) {                            for (int i = mRoots.size() - 1; i >= 0; --i) {                                mRoots.get(i).loadSystemProperties();                            }                        }                    }                };                SystemProperties.addChangeCallback(mSystemPropertyUpdater);            }            int index = findViewLocked(view, false);            if (index >= 0) {                if (mDyingViews.contains(view)) {                    // Don't wait for MSG_DIE to make it's way through root's queue.                    mRoots.get(index).doDie();                } else {                    throw new IllegalStateException("View " + view                            + " has already been added to the window manager.");                }                // The previous removeView() had not completed executing. Now it has.            }            // If this is a panel window, then find the window it is being            // attached to for future reference.            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {                final int count = mViews.size();                for (int i = 0; i < count; i++) {                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {                        panelParentView = mViews.get(i);                    }                }            }            root = new ViewRootImpl(view.getContext(), display);            view.setLayoutParams(wparams);            mViews.add(view);            mRoots.add(root);            mParams.add(wparams);        }        // do this last because it fires off messages to start doing things        try {            root.setView(view, wparams, panelParentView);        } catch (RuntimeException e) {            // BadTokenException or InvalidDisplayException, clean up.            synchronized (mLock) {                final int index = findViewLocked(view, false);                if (index >= 0) {                    removeViewLocked(index, true);                }            }            throw e;        }    }

addView方法主要分为如下几步:

1、检查参数是否合法

 if (view == null) {            throw new IllegalArgumentException("view must not be null");        }        if (display == null) {            throw new IllegalArgumentException("display must not be null");        }        if (!(params instanceof WindowManager.LayoutParams)) {            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");        }        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;        if (parentWindow != null) {            parentWindow.adjustLayoutParamsForSubWindow(wparams);        }

2、创建ViewRootImpl并将View添加到列表中

在WindowManagerGlobal内部有如下几个列表比较重要:

    private final ArrayList<View> mViews = new ArrayList<View>();    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();    private final ArrayList<WindowManager.LayoutParams> mParams =            new ArrayList<WindowManager.LayoutParams>();    private final ArraySet<View> mDyingViews = new ArraySet<View>();

在上面的声明中吗,mViews存储的是所有Window所对应的View,mRoots存储的是所有Window所对应的ViewRootImpl,mParams存储的是所有Window所对应的布局参数,而mDyingViews存储了那些正在被删除的View对象,或者说是那些已经调用removeView方法但是还没有删除的Window对象。在addView方法中通过如下方式将Window的一系列对象添加到列表中

            root = new ViewRootImpl(view.getContext(), display);            view.setLayoutParams(wparams);            mViews.add(view);            mRoots.add(root);            mParams.add(wparams);
3、通过ViewRootImpl来更新界面并完成Window的添加过程

这个步骤由ViewRootImpl的setView方法来完成,

root.setView(view, wparams, panelParentView);

在setView内部会通过requestLayout来完成异步刷新请求,requestLayout最终会调用performTraversals方法来完成View的绘制,源码注释如下:差不多意思就是在添加Window之前先完成第一次layout布局过程,以确保在收到任何系统事件后面重新布局。

                // Schedule the first layout -before- adding to the window                // manager, to make sure we do the relayout before receiving                // any other events from the system.                requestLayout();

接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中mWindowSession类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是说这其实是一次IPC过程,远程调用了Session中的addToDisPlay方法。

try {                    mOrigWindowType = mWindowAttributes.type;                    mAttachInfo.mRecomputeGlobalAttributes = true;                    collectViewAttributes();                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                            getHostVisibility(), mDisplay.getDisplayId(),                            mAttachInfo.mContentInsets, mInputChannel);                } catch (RemoteException e) {                    mAdded = false;                    mView = null;                    mAttachInfo.mRootView = null;                    mInputChannel = null;                    mFallbackEventHandler.setView(null);                    unscheduleTraversals();                    setAccessibilityFocus(null, null);                    throw new RuntimeException("Adding window failed", e);                }

Session中的addToDisPlay方法如下:Session这个类在package com.android.server.wm

    @Override    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,            int viewVisibility, int displayId, Rect outContentInsets,            InputChannel outInputChannel) {        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,                outContentInsets, outInputChannel);    }
可以看出,Window的添加请求就交给WindowManagerService去处理了。addView大概一个过程如下:

WindowManager——>WindowManagerGobal——>ViewRootImpl——>Session——>WindowManagerService

那么WindowManager又是如何与DecorView相连的呢,最终DecorView肯定是要添加到Window上的,而Window的具体实现类是PhoneWindow,因为DecorView嵌入在Window上,如图所示:


在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联,可以参考一下代码,在ActvityThread中,也就是ViewRootImpl是DecorView的父元素,但是ViewRootImpl并不是View。

<span style="white-space:pre"></span>r.window = r.activity.getWindow();                View decor = r.window.getDecorView();//获得DecorView                decor.setVisibility(View.INVISIBLE);                ViewManager wm = a.getWindowManager();                WindowManager.LayoutParams l = r.window.getAttributes();                a.mDecor = decor;                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;                l.softInputMode |= forwardBit;                if (a.mVisibleFromClient) {                    a.mWindowAdded = true;                    wm.addView(decor, l);//通过WindowManager添加decorView                }


我们平时在Activity调用setContentView会调用PhoneWindow的setContentView,最后会调用DecorView的addView方法,这也说明了我们添加的View是DecorView的子元素。




二、完成View的绘制过程


整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法(这个方法巨长)开始的,该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个,如下:

private void performTraversals() {        ......        //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来        //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);        ......        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);        ......        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());        ......        mView.draw(canvas);        ......    }

performTraversals方法会经过measure、layout和draw三个过程才能将一个View绘制出来,所以View的绘制是ViewRootImpl完成的,另外当手动调用invalidate,postInvalidate,requestInvalidate也会最终调用performTraversals,来重新绘制View。其中requestLayout()方法会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身。




三、向DecorView分发事件。

这里的事件不仅仅包括MotionEvent,还有KeyEvent。我们知道View的时间分发顺序为Activity——>Window——>View,那么Activity的事件来源在哪里呢?这是个需要思考的问题,答案和ViewRootImpl有很大的关系。

首先,事件的根本来源来自于Native层的嵌入式硬件,然后会经过InputEventReceiver接受事件,然后交给ViewRootImpl,将事件传递给DecorView,DecorView再交给PhoneWindow,PhoneWindow再交给Activity。这样看来,整个体系的事件分发顺序为:




那么这一过程又是怎么实现的呢?

首先看ViewRootImpl的dispatchInputEvent方法。

    public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {        SomeArgs args = SomeArgs.obtain();        args.arg1 = event;        args.arg2 = receiver;        Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);        msg.setAsynchronous(true);        mHandler.sendMessage(msg);    }

InputEvent输入事件,它有2个子类:KeyEvent和MotionEvent,其中KeyEvent表示键盘事件,而MotionEvent表示点击事件,这里InputEventReceiver译为输入事件接收者,顾名思义,就是用于接收输入事件,然后交给ViewRootImpl的dispatchInputEvent方法去分发处理。可以看到mHandler将逻辑切换到UI线程,代码如下。

    final ViewRootHandler mHandler = new ViewRootHandler();
<pre name="code" class="java"> final class ViewRootHandler extends Handler {         @Override        public void handleMessage(Message msg) {            switch (msg.what) {                     ........           {SomeArgs args = (SomeArgs)msg.obj;</span>                InputEvent event = (InputEvent)args.arg1;                InputEventReceiver receiver = (InputEventReceiver)args.arg2;                enqueueInputEvent(event, receiver, 0, true);                args.recycle();            } break;.................   }


{
在mHandler的UI线程中,最终调用了enqueueInputEvent方法,该方法就是将输入事件打包,利用InputEvent,InputEventReceiver构造对象QueueInputEvent,然后加入到待处理的事件队列中,代码如下:

    void enqueueInputEvent(InputEvent event,            InputEventReceiver receiver, int flags, boolean processImmediately) {        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);        // Always enqueue the input event in order, regardless of its time stamp.        // We do this because the application or the IME may inject key events        // in response to touch events and we want to ensure that the injected keys        // are processed in the order they were received and we cannot trust that        // the time stamp of injected events are monotonic.        QueuedInputEvent last = mPendingInputEventTail;        if (last == null) {            mPendingInputEventHead = q;            mPendingInputEventTail = q;        } else {            last.mNext = q;            mPendingInputEventTail = q;        }        mPendingInputEventCount += 1;        Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,                mPendingInputEventCount);        if (processImmediately) {            doProcessInputEvents();        } else {            scheduleProcessInputEvents();        }    }

enqueueInputEvent方法又会调用doProcessInputEvents方法或者scheduleProcessInputEvents方法,这其实是同步或者同步处理消息队列的,同步或者异步根据传入的标志位processImmediately来判断。scheduleProcessInputEvents方法只是利用mHandler向UI线程发送了一个message,代码如下:

    private void scheduleProcessInputEvents() {        if (!mProcessInputEventsScheduled) {            mProcessInputEventsScheduled = true;            Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);            msg.setAsynchronous(true);            mHandler.sendMessage(msg);        }    }

UI线程处理的代码为:

<span style="font-family:Comic Sans MS;">            case MSG_PROCESS_INPUT_EVENTS:                mProcessInputEventsScheduled = false;                doProcessInputEvents();                brea</span>
这下就很清楚了,即使是调用了scheduleProcessInputEvents方法,最终还是会调用doProcessInputEvents方法,只是同步与异步的区别。doProcessInputEvents的代码如下:

    void doProcessInputEvents() {        // Deliver all pending input events in the queue.        while (mPendingInputEventHead != null) {            QueuedInputEvent q = mPendingInputEventHead;            mPendingInputEventHead = q.mNext;            if (mPendingInputEventHead == null) {                mPendingInputEventTail = null;            }            q.mNext = null;            mPendingInputEventCount -= 1;            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,                    mPendingInputEventCount);            deliverInputEvent(q);        }        // We are done processing all input events that we can process right now        // so we can clear the pending flag immediately.        if (mProcessInputEventsScheduled) {            mProcessInputEventsScheduled = false;            mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);        }    }

代码中的注释已经写得很清楚了,就是逐个分发输入事件队列中的事件,分发一个便从队列中删除,其实就是单链表的操作,分发过程过程用过deliverInputEvent方法,当队列中的输入事件都已经处理完,就立即清除标志位。deliverInputEvent方法如下:

    private void deliverInputEvent(QueuedInputEvent q) {        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",                q.mEvent.getSequenceNumber());        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);        }        InputStage stage;        if (q.shouldSendToSynthesizer()) {            stage = mSyntheticInputStage;        } else {            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;        }        if (stage != null) {            stage.deliver(q);        } else {            finishInputEvent(q);        }    }
在ViewRootImpl中,有一系列类似于InputStage(输入事件舞台)的概念,他是一个抽象类,它的deliver方法会处理一个输入事件。处理完成之后会调用finishInputEvent方法。

它有很多子类,对应具体的InputStage,每种InputStage可以处理一定的事件类型,比如AsyncInputStage、SyntheticInputStage、NativePostImeInputStage、ViewPreImeInputStage、ViewPostImeInputStage等,它的子类实现了InputStage的一些抽象方法,比如onProcess、onDeliverToNext、processKeyEvent、processPointerEvent、processTrackballEvent、processGenericMotionEvent,从这些方法大概可以看出意思,在不同的情况下,onProcess、onDeliverToNext方法就会被回调。


当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理。对于点击事件来说,ViewPostImeInputStage可以处理它,ViewPostImeInputStage中,ViewPostImeInputStage类中的onProcess方法如下。onProcess被回调时,processKeyEvent、processPointerEvent、processTrackballEvent、processGenericMotionEvent至少有一个方法就会被调用,这些方法都是属于ViewPostImeInputStage的。

        @Override        protected int onProcess(QueuedInputEvent q) {            if (q.mEvent instanceof KeyEvent) {                return processKeyEvent(q);            } else {                // If delivering a new non-key event, make sure the window is                // now allowed to start updating.                handleDispatchDoneAnimating();                final int source = q.mEvent.getSource();                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {                    return processPointerEvent(q);                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {                    return processTrackballEvent(q);                } else {                    return processGenericMotionEvent(q);                }        

在processKeyEvent、processPointerEvent、processTrackballEvent、processGenericMotionEvent方法中都有一句很关键的一句代码;

mView.dispatchKeyEvent(event)//按键事件
mView.dispatchPointerEvent(event)
mView.dispatchTrackballEvent(event)

mView.dispatchGenericMotionEvent(event)
View mView;

可以看到mView并没有实例化,其实他的实例化在ViewRootImpl的setView方法中,

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {        synchronized (this) {            if (mView == null) {                mView = view;.............................}}}

可以看出,mView的实例化是在setView方法中完成,而我们知道ViewRootImpl的setView方法中传入的view参数是DecorView,因为ViewRootImpl通过setView方法将DecorView添加到PhoneWindow的。

所以这里的mView其实就是DecorView。

这样一来,可以知道ViewPostImeInputStage将事件分发到了View,而这里的mView又是DecorView,也就是多态的原理,如果DecorView没有上述的mView.的几个方法,就会调用View的方法,如果DecorView实现了就会调用DecorView的方法继续跟踪源码,其中在View的dispatchPointerEvent方法如下:

public final boolean dispatchPointerEvent(MotionEvent event) {    if (event.isTouchEvent()) {        return dispatchTouchEvent(event);    } else {        return dispatchGenericMotionEvent(event);    }}

会调用dispatchTouchEvent方法,根据多态的特性,最终点击事件会传给给DecorView的dispatchTouchEvent方法。下面再看DecorView的dispatchTouchEvent方法。

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    final Callback cb = getCallback();    return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)            : super.dispatchTouchEvent(ev);}
可以看出DecorView最终会调用cb.dispatchTouchEvent方法,那么问题问题又来了,这个Callback是什么,其实这个Callback就是当前的Activity。

首先Activity继承了Window.Callback

public class Activity extends ContextThemeWrapper        implements LayoutInflater.Factory2,        Window.Callback, KeyEvent.Callback,        OnCreateContextMenuListener, ComponentCallbacks2,        Window.OnWindowDismissedCallback {
然后Window.Callback的代码如下:

public interface Callback {    /**     * Called when action mode is first created. The menu supplied will be used to     * generate action buttons for the action mode.     *     * @param mode ActionMode being created     * @param menu Menu used to populate action buttons     * @return true if the action mode should be created, false if entering this     *              mode should be aborted.     */    public boolean onCreateActionMode(ActionMode mode, Menu menu);    /**     * Called to refresh an action mode's action menu whenever it is invalidated.     *     * @param mode ActionMode being prepared     * @param menu Menu used to populate action buttons     * @return true if the menu or action mode was updated, false otherwise.     */    public boolean onPrepareActionMode(ActionMode mode, Menu menu);    /**     * Called to report a user click on an action button.     *     * @param mode The current ActionMode     * @param item The item that was clicked     * @return true if this callback handled the event, false if the standard MenuItem     *          invocation should continue.     */    public boolean onActionItemClicked(ActionMode mode, MenuItem item);    /**     * Called when an action mode is about to be exited and destroyed.     *     * @param mode The current ActionMode being destroyed     */    public void onDestroyActionMode(ActionMode mode);}
所以Activity必须要实现Window.Callback当中的方法,基本都是事件传递相关的,其中就是dispatchTouchEvent方法,在Activity的 attach方法中有如下一段代码:

final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {        attachBaseContext(context);        mFragments.attachHost(null /*parent*/);        mWindow = new PhoneWindow(this);        mWindow.setCallback(this);        mWindow.setOnWindowDismissedCallback(this);        mWindow.getLayoutInflater().setPrivateFactory(this);

PhoneWindow通过设置setCallback将Callback设置为this也就是Activity。


至此点击事件已经到了Activity,那么后面就和上面博客事件分发机制一样了。事件分发机制





8 0
原创粉丝点击