Bootstrap

Android窗口机制:四、ViewRootImpl的创建以及视图真正加载。(源码版本SDK31)

Android 窗口机制 SDK31源码分析 总目录

通过上一章我们知道在handleResumeActivity方法中,最终将将DecorView通过addView方法被添加到了WindowManager之中了。那么这里面干了什么呢?

这里涉及到一个重要的类ViewRootImpl,本章详细解析ViewRootImpl的创建以及视图真正的加载过

ViewRootImpl

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
            ...
            //持有主线程
	        final Thread mThread;
            //DecorView
            View mView;
            final View.AttachInfo mAttachInfo;
            ...
            //执行setView方法去想Window添加View
            public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {...}
            void doTraversal() {...}
            void scheduleTraversals() {...}
            ...
}

差不多见名识意吧:

  • 它会在WindowManager调用addView添加DecorView时进行初始化,能和系统的WindowManagerService交互,管理DecorView 的绘图和窗口状态;会持有DecorView;
  • 会进行绘制主线程检查,异步线程抛异常;
  • 会进行我们所熟知视图绘制三大流程,performMeasure、performLayout、performDraw;
  • 在View中会被mParent变量所持有,在View.AttachInfo中被mViewRootImpl变量所持有,我们平时调用的getViewRootImpl()方法,就是获取的mViewRootImpl变量的引用。
  • 调用View的invalidate()方法也会到ViewRootImpl里面,另外,除了绘制流程,它还承担了事件分发(即输入事件中转站,下一章节讲到)。
  • 负责和WMS进行进程间通信。

源码调用分析

通过上一章节分析我们知道,WindowManager调用addView方法进行管理DecorView。我们又知道最终肯定是通过WindowManagerGlobal来进行实际的执行,所以我们直接看WindowManagerGlobal的addView方法:

    WindowManagerGlobal
	public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ...

        ViewRootImpl root;
        View panelParentView = null;
		...
            root = new ViewRootImpl(view.getContext(), display);
            //保存ViewRootImpl、DecorView以及params
            mViews.add(view);
            mRoots.add(root);
        	mParams.add(wparams);
            try {
                //调用ViewRootImpl的setView方法
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

addView中我们看到了ViewRootImpl,首先构建ViewRootImpl,之后通过setView方法,将DecorView传递到了ViewRootImpl

另WindowManagerGlobal中维护的和Window操作相关的3个列表,在窗口的添加、更新和删除过程中都会涉及这3个列表,它们分别是View 列表(ArrayList<View> mViews)、布局参数列表(ArrayList<WindowManager.LayoutParams>mParams)和ViewRootImpl列表(ArrayList<ViewRootImpl>mRoots

ViewRootImpl的构建以及setView的源码:

//ViewRootImpl构造方法
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
	...
   	//持有主线程,即创建ViewRootImpl的线程
    mThread = Thread.currentThread();
    //初始化AttachInfo,内部传递持有了ViewRootImpl,后续的代码里,它将被传递给View,所以可以通过View获取到mAttachInfo,进而获取到ViewRootImpl。
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
}
ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                //持有DecorView
                mView = view;
                ...
                //AttachInfo持有DecorView
                mAttachInfo.mRootView = view;
                //调用requestLayout
                requestLayout();
                ...
                    //通过WindowSession来完成window最终的添加,该过程mWindowSession类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,所以这其实是一次IPC的过程,远程的调用了Session的addToDisPlay方法。
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                            mTempControls);
				...
               	//将自己传递给View的mParent变量,如此调用View相关的invalidate方法,就直接调用到了ViewRootImpl
                view.assignParent(this);
                ...
        }
    }

除了相关变量的赋值操作,在setView中有两个很值得注意的地方。

  1. 调用了requestLayout方法,其内部会调用到scheduleTraversals,进而调用了measure、layout、draw
  2. 通过IPC,将window添加到WMS(WindowManagerService)进行管理。

针对将window添加到WMS这一块,我们可以看一下Session的代码:

Session
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
    int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
    InputChannel outInputChannel, InsetsState outInsetsState,
    InsetsSourceControl[] outActiveControls) {
    return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
    requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
}

这个mService就是WMS,也就是说ViewRootImpl通过Session可以和WMS进行跨进程调用,而我们看到Session也被WMS持有,每个应用程序进程都会对应一个Session,WMS会用ArrayList来保存这些Session。剩下的工作就交给WMS来处理,在WMS中会为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS 会将它所管理的Surface 交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。

DecorView的绘制流程则是通过调用requestLayout来继续完成的

ViewRootImpl
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        //判断当前是否是主线程即mThread,如果不是,则抛异常。就是在子线程更新UI没使用handler的话就会抛出的异常
        checkThread();
        //设置mLayoutRequested为true。
        mLayoutRequested = true;
        //进而测量、布局、绘制
        scheduleTraversals();
    }
}

requestLayout的方法中,首先进行调用者的线程检查,如果不是主线程,即不和mThread指向的不是同一个线程对象,就抛出异常。随后设置mLayoutRequested为true,这个变量是做什么呢?它主要用来区分是否需要进行测量和布局。最后调用mLayoutRequested方法。

这里说明一下,requestLayout方法和invalidae方法均是通过最后均调用了scheduleTraversals方法,那么是如何区分是不是需要测量和布局呢?就是上面提到的mLayoutRequested变量了,如果是requestLayout则赋值为true,就会调用测量和布局;如果是invalidae,则默认就为false。

接下来看一下scheduleTraversals及其相关方法的调用流程。

    ViewRootImpl
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
	mTraversalRunnable
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
	ViewRootImpl
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            //调用了performTraversals
            performTraversals();
        }
    }
	ViewRootImpl
    private void performTraversals() {
        final View host = mView;
        ...
        //将mAttachInfo传入View,同时会调用View的onAttachedToWindow方法
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        ...
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
        performLayout(lp, mWidth, mHeight);
        ...
        performDraw();
        ...

上面列出了scheduleTraversals整个方法的调用链,下面来梳理一下:

  1. 给Choreographer注册了一个mTraversalRunnable。Choreographer用于接收显示系统的VSync信号,在下一个帧渲染时控制执行一些操作。Choreographer的postCallback方法用于发起添加回调,这个添加的回调将在下一帧被渲染时执行。所以我们知道mTraversalRunnable里面的内容将在下一帧渲染时被执行。
  2. 我们看一下mTraversalRunnable的类型,内部调用了doTraversal。而在doTraversal内部调用了performTraversals
  3. performTraversals中,调用DecorView 的dispatchAttachedToWindow方法,给View绑定了mAttachInfo,进而可以调用到View的onAttachedToWindow方法。之后就是熟悉的测量、布局、绘制调用流程。

ViewRootImpl的创建以及视图绘制流程就清楚了,和之前的结合进行简单的总结一下:

handleLaunchActivity中进行Activity的实例化,之后初始化WindowManager,Context等,Window持有WindowManager,给Activity绑定Window对象。然后调用到onCreate的setContentView,创建DecorView被Window持有。同时我们自己的布局包含在了DecorView中;之后调用handleStartActivity方法,里面会调用onStart以及onRestoreInstanceState

之后真正的显示过程在调用handleResumeActivity回调调用onResume之后,此时会将DecorView通过WindowManager的addView方法和WindowManager进行绑定,利用ViewRootImpl进行和WMS交互,加入window布局到到WMS,然后进行View的测量、布局、绘制,最后调用Activity的makeVisible进行显示。

下一章节预告:ViewRootImpl的事件分发

创作不易,如有帮助一键三连咯🙆‍♀️。欢迎技术探讨噢!

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;