ViewRootImpl
之前反复提到过的 DecorView 不是整个 View 树的根吗?怎么又出来一个看起来像是『根』的东西?
我们看看 ViewRootImpl 的代码,就能明白 Android 为什么要添加两个『根』在树上。
我们先看看 requestLayout()
方法究竟在做什么:
@Override public void requestLayout () { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true ; scheduleTraversals(); } }void scheduleTraversals () { if (!mTraversalScheduled) { mTraversalScheduled = true ; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null ); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }final class TraversalRunnable implements Runnable { @Override public void run () { doTraversal(); } }void doTraversal () { if (mTraversalScheduled) { mTraversalScheduled = false ; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor" ); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false ; } } }
好,看到一个 Android 中一个比较重要的方法 performTraversals()
,实现了 View 树的『从根到叶』的遍历。ViewRootImpl 中接收的各种变化,如来自 WMS 的窗口属性变化、来自控件树的尺寸变化以及重绘请求等都引发performTraversals()
的调用,并在其中完成处理。View 类及其子类的 onMeasure()
、onLayout()
、onDraw()
等回调也都是在该方法执行的过程中直接或间接的引发。该函数可谓是是 ViewRootImpl 的『心跳』。我们就来看一下这个方法。
private void performTraversals () { ... boolean windowSizeMayChange = false ; boolean surfaceChanged = false ; WindowManager.LayoutParams lp = mWindowAttributes; int desiredWindowWidth; int desiredWindowHeight; ... Rect frame = mWinFrame; if (mFirst) { mFullRedrawNeeded = true ; mLayoutRequested = true ; final Configuration config = mContext.getResources().getConfiguration(); if (shouldUseDisplaySize(lp)) { Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { desiredWindowWidth = mWinFrame.width(); desiredWindowHeight = mWinFrame.height(); } ... } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { mFullRedrawNeeded = true ; mLayoutRequested = true ; windowSizeMayChange = true ; } } ... boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); if (layoutRequested) { final Resources res = mView.getContext().getResources(); if (mFirst) { ... } else { if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowSizeMayChange = true ; if (shouldUseDisplaySize(lp)) { Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { Configuration config = res.getConfiguration(); desiredWindowWidth = dipToPx(config.screenWidthDp); desiredWindowHeight = dipToPx(config.screenHeightDp); } } } windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); } ... if (layoutRequested) { mLayoutRequested = false ; } boolean windowShouldResize = layoutRequested && windowSizeMayChange && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT && frame.width() < desiredWindowWidth && frame.width() != mWidth) || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT && frame.height() < desiredWindowHeight && frame.height() != mHeight)); windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM; ... if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) { ... boolean hadSurface = mSurface.isValid(); try { ... relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); ... } catch (RemoteException e) { } ... if (!mStopped || mReportNextDraw) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0 ); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth=" + mWidth + "measuredWidth=" + host.getMeasuredWidth() + "mHeight=" + mHeight + "measuredHeight=" + host.getMeasuredHeight() + "coveredInsetsChanged=" + contentInsetsChanged); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); boolean measureAgain = false ; if (lp.horizontalWeight > 0.0f ) { width += (int ) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain = true ; } if (lp.verticalWeight > 0.0f ) { height += (int ) ((mHeight - height) * lp.verticalWeight); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); measureAgain = true ; } if (measureAgain) { if (DEBUG_LAYOUT) Log.v(mTag, "And hey let's measure once more: width=" + width + " height=" + height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } layoutRequested = true ; } } } else { maybeHandleWindowMove(frame); } if (surfaceSizeChanged) { updateBoundsSurface(); } final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); ... if (didLayout) { performLayout(lp, mWidth, mHeight); if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0 ) { host.getLocationInWindow(mTmpLocation); mTransparentRegion.set(mTmpLocation[0 ], mTmpLocation[1 ], mTmpLocation[0 ] + host.mRight - host.mLeft, mTmpLocation[1 ] + host.mBottom - host.mTop); host.gatherTransparentRegion(mTransparentRegion); if (mTranslator != null ) { mTranslator.translateRegionInWindowToScreen(mTransparentRegion); } if (!mTransparentRegion.equals(mPreviousTransparentRegion)) { mPreviousTransparentRegion.set(mTransparentRegion); mFullRedrawNeeded = true ; try { mWindowSession.setTransparentRegion(mWindow, mTransparentRegion); } catch (RemoteException e) { } } } if (DBG) { System.out.println("======================================" ); System.out.println("performTraversals -- after setFrame" ); host.debug(); } } boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; if (!cancelDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0 ) { for (int i = 0 ; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); } else { if (isViewVisible) { scheduleTraversals(); } else if (mPendingTransitions != null && mPendingTransitions.size() > 0 ) { for (int i = 0 ; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); } } mIsInTraversal = false ; }
好吧,我承认,这是我见过的最长的方法了。只能给它分为几个重要阶段,并提炼一下,无法进行太详细的分析了。
I. pre-measure 阶段
这是第一个阶段,它会对控件树进行第一次测量。在此阶段中将会计算出控件树为显示其内容所需的尺寸,即期望的窗口尺寸。在这个阶段中 View 及其子类的 onMeasure()
方法将会沿着控件树依次得到回调。
预测量也是一次完整的测量过程,它与最终测量的区别仅在于参数不同而已。实际的测量工作是在 View 或其子类的 onMeasure()
方法中完成,并且其测量结果需要受限于来自其父控件的指示。这个指示由 onMeasure()
方法中的两个参数进行传达:widthSpec
和heightSpec
。它们是被称为 MeasureSpec 的复合整型变量,用于指导控件对自身进行测量。它又两个分量,结构如下表:
32, 31
30 ... 1
SPEC_MODE
SPEC_SIZE
其实就是个 32 位的整型,高 2 位用于储存它的类型,后 30 位用于储存它的内容。
I、II、III 三个阶段可知预测量时的 SPEC_SIZE 按照如下原则进行取值:
第一次“遍历”时,使用可用的最大尺寸作为 SPEC_SIZE 的候选
此窗口是一个悬浮窗口时,即 LayoutParams.width/height 其中之一被指定为 WRAP_CONTENT 时,使用可用的最大尺寸作为 SPEC_SIZE 的候选
其他情况下,使用窗口最新尺寸作为 SPEC_SIZE 的候选