ViewRootImpl

时间:2024-07-18 07:18:20

这是什么东西?如果已经看过源码的同学,亦或者有多年开发经验的人,自然不必多说。但如果没有,这里建议先看一下Android基础知识之Window(一)

WindowManagerGlobal

从上篇文章末尾可以知道,View的添加、更新、删除操作是WindowManagerGlobal来实现的。那么我们就看看这三个方法到底做了什么吧!我们一段一段分析

// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        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, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;
        
		/** 省略  **/

        if (windowlessSession == null) {
            root = new ViewRootImpl(view.getContext(), display);
        } else {
            root = new ViewRootImpl(view.getContext(), display,
                    windowlessSession, new WindowlessWindowLayout());
        }
		// (1)view设置layoutparams参数
        view.setLayoutParams(wparams);
		// (2)添加view、root、wparams到它们各自的集合中去
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
        	// (3)root(ViewRootImpl)调用它的setView方法
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
            // BadTokenException or InvalidDisplayException, clean up.
            if (viewIndex >= 0) {
                removeViewLocked(viewIndex, true);
            }
            throw e;
        }
    }

先看添加view,平常使用ViewGroup#addView会传递View和params两个参数,在这里的addView方法中,我们也可以看到(1),把传入的layoutParams设置给传入的view。接着(2)管理了三个集合,用来存储添加的view、root(ViewRootImpl)、params。最后(3)调用创建的ViewRootImpl的setView方法,把addView接收的大部分的参数传递给它。这里我们第一次接触到了ViewRootImpl这个类。

通过上述源码分析可以看出,每次Windwo#addView(常见的是Activity#setContentView)的时候,都会创建一个ViewRootImpl对象,并且把它缓存起来。通过使用三个ArrayList集合缓存View、Root、Params,可以在其他操作的时候,从缓存中快速取出与之对应的对象进行操作。

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

根据updateViewLayout和removeView两个方法,也确实证实了,从mRoots取出对应的ViewRootImpl进行的操作。

setView

总算进来了ViewRootImpl,这里只跟踪setView的流程,关于updateViewLayout和removeView可以自行分析一下。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
       int userId) {
	/**  省略  **/
	
// 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();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;

                if (mView instanceof RootViewSurfaceTaker) {
                    PendingInsetsController pendingInsetsController =
                            ((RootViewSurfaceTaker) mView).providePendingInsetsController();
                    if (pendingInsetsController != null) {
                        pendingInsetsController.replayAndAttach(mInsetsController);
                    }
                }

                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    adjustLayoutParamsForCompatibility(mWindowAttributes);
                    controlInsetsForCompatibility(mWindowAttributes);

                    Rect attachedFrame = new Rect();
                    final float[] compatScale = { 1f };
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
                            mTempControls, attachedFrame, compatScale);
                    if (!attachedFrame.isValid()) {
                        attachedFrame = null;
                    }
                    if (mTranslator != null) {
                        mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
                        mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());
                        mTranslator.translateRectInScreenToAppWindow(attachedFrame);
                    }
                    mTmpFrames.attachedFrame = attachedFrame;
                    mTmpFrames.compatScale = compatScale[0];
                    mInvCompatScale = 1f / compatScale[0];
                } catch (RemoteException | RuntimeException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

	/**  省略  **/
}

这里的代码篇幅有点长,所以省略了很多相对不是重要的内容。上述代码需要关心两个地方,其一是requestLayout这个方法;这其二是mWindowSession.addToDisplayAsUser这个调用。

首先说一下第二点,因为这次只讲ViewRootImpl,不会讲到WMS去,这个调用会一笔带过,重点会讲一下requestLayout这个函数。

mWindowSession.addToDisplayAsUser是一个binder通信,如果还有不了解的,可以去补一下binder相关的知识。刚刚在讲WindowManagerGlobal的时候,漏了一个东西就是它持有了WMS(WindowManagerService)的代理对象。这是一个静态方法,它不仅仅是WindowManagerGlobal自己使用,还提供给外部,比如ViewRootImpl使用,通过getWindowSession就可以拿到WMS的代理对象进行binder通信。

// WindowManagerGlobal.java
    @UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

显然addToDisplayAsUser就是调用WMS的addToDisplay相关的方法,传入了window,window属性,displayId,InputChannel等信息下去。相关内容在下一篇文章会讲到。这里用黑体加粗了InputChannel,也是一个很重要的概念,之后写相关事件输入的文章会好好讲讲。

接着我们来看requestLayout的源码是怎样实现的

// ViewRootImpl.java

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这两段代码乍得一看很短,但实际上看也的确很短,可是能讲的内容确实很多的。我们慢慢分析,首先看到checkThread,这个有点熟悉。

    void checkThread() {
        Thread current = Thread.currentThread();
        if (mThread != current) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views."
                            + " Expected: " + mThread.getName()
                            + " Calling: " + current.getName());
        }
    }

关于这个打印,我们是不是很熟悉,获取当前线程,判断当前线程会创建View的线程是不是同一个,否则抛出异常。这个打印应该不少见到过。

接着执行scheduleTraversals(安排遍历),这里的遍历是遍历什么呢【1】?这里我们先不表。继续看它里面做了什么。设置状态我们先不管,可以看到Handler先获取了Looper,再获取了Queue,然后调用了postSyncBarrier(插入同步屏障),这个Handler是主线程创建的Handler,所以默认使用的是主线程的Looper,通过消息队列插入了一个同步屏障,也就是说拦截了所有的同步消息,接下来会优先处理异步消息。这是为什么呢【2】?(这里有对同步屏障不了解的可以去看看Handler有关的内容)

再然后通过Choreography#postCallback发送一个回调,传入mTraversalRunnable来接收这个回调。这里有个重点,Choreography#postCallback可以理解为是应用层去请求Vsync信号的过程,我们知道Vsync信号在16.67ms会上来一次,也就是说这个回调每16.67ms period会上来一次。这里的Vsync信号概念以后会详细的讲解一下,这里暂时可以这么去理解。

最后通知render thread(渲染线程)去等待处理。单单这很短一段代码,我们已经有很多疑问了。我们来一一揭开。

doTraversals
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

刚刚上面有提到,这个runnable每16.67ms周期上来一次,每次都会执行doTraversal(开始遍历)。在遍历执行前,会移除以前添加的同步屏障,这短短一个16.67ms周期,就先添加屏障,再移除屏障,这里就解释了之前的第二点问题,同步屏障是为了处理异步消息的,而postCallback回调的就是Vsync信号,那么由此可见Vsync信号上来的方式是异步消息的方式发送上来的。然后开始执行performTraversals(执行遍历)

 private void performTraversals() {
 		/**  省略  **/
 	                if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()
                        || dispatchApplyInsets || updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
                            lp.privateFlags);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
                            lp.privateFlags);

                    if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                            + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                            + " mHeight=" + mHeight
                            + " measuredHeight=" + host.getMeasuredHeight()
                            + " dispatchApplyInsets=" + dispatchApplyInsets);

                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
 		
 		/**  省略  **/
		final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            performLayout(lp, mWidth, mHeight);  
        }    

		/**  省略  **/  
		if (!performDraw() && mActiveSurfaceSyncGroup != null) {
            mActiveSurfaceSyncGroup.markSyncReady();
        } 
}

由于这个方法实在是长,因此省略了很多细节的东西。但根据源码来看,主要函数就这三个performMeasure - performLayout - performDraw。这里解开了之前说的第一个问题,为什么是安排遍历,performMeasure、performLayout 、performDraw都会根据ViewTree递归调用它们的子View,依次循环的执行它们的measure、layout、draw。

  • 首先执行测量,根据官方给的注解也可以看出来,测量的是视图大小,传入高宽的MeasureSpec,这个值有32位,高两位是表示View的约束模式,低三十位是表示测量的大小数值,执行View#onMeasure的测量逻辑
  • 接着执行布局,可以理解为摆放位置,根据起始坐标和高宽,确定整个view的位置,执行view#onLayout的布局逻辑
  • 最后执行绘制,判断是否开启了硬件支持,决定使用硬件绘制还是软件绘制,执行view#onDraw里的逻辑开始绘制
void performDraw() {
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (isHardwareEnabled()) {
            // 硬件加速绘制
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
        } else {
            // 软件绘制
            drawSoftware();
        }
    }
}

我们在分析一下这段代码,通过硬件支持的判断,执行硬件加速绘制和软件绘制。先说软件绘制,ViewRootImpl管理了一个Surface和SurfaceControl,经过软件绘制,View会绘制到Canvas上面,这个Canvas是Surface提供的,也就是说明绘制到了Surface上面。再说说硬件绘制,由ThreadedRenderer#draw进行绘制,通过view获取displayList,一个显示列表,由显示列表获取Canvas执行View#onDraw,最后将绘制命令提交给GPU,执行render开始绘制。

软件绘制
View#onDraw - Canvas - Surface - ViewRootImpl
硬件绘制
View#onDraw - Canvas - Surface - displayList - ThreadedRenderer