之前反复提到过的DecorView不是整个View树的根吗?怎么又出来一个看起来像是『根』的东西?
我们看看ViewRootImpl的代码,就能明白Android为什么要添加两个『根』在树上。
我们先看看requestLayout()
方法究竟在做什么:
// ViewRoomImpl.java
@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;
// I. pre-measure阶段
// 这两个变量就是DecorViewSPEC_SIZE的候选
int desiredWindowWidth;
int desiredWindowHeight;
...
Rect frame = mWinFrame;
// 是否是『第一次遍历』
// 在ViewRootImpl的生命周期里,会有很多次的『遍历』,第一次的 desiredWindowWidth / desiredWindowHeight
// 与之后的 desiredWindowWidth / desiredWindowHeight 的测量方式是不一样的
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
final Configuration config = mContext.getResources().getConfiguration();
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
// 第1次“遍历”的测量,采用了应用可以使用的最大尺寸作为SPEC_SIZE的候选
desiredWindowWidth = mWinFrame.width();
desiredWindowHeight = mWinFrame.height();
}
...
} else {
// 在非第1次遍历的情况下,会采用窗口的最新尺寸作为SPEC_SIZE的候选
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
//如果窗口的最新尺寸与ViewRootImpl中的现有尺寸不同,说明WMS单方面改变了窗口的尺寸,将导致下面三个结果
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 {
// 检查WMS是否单方面改变了一些参数,标记下来,然后作为之后是否进行控件布局的条件之一
// 如果窗口的width或height被指定为WRAP_CONTENT时。表示该窗口为悬浮窗口
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
// 标记一下,WMS单方面改变了一些参数
windowSizeMayChange = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
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) {
// 如果后面还有layout的请求的话,用这个FLAG来标记,然后再重新来一次layout
// 所以现在先置为false
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;
...
// I. pre-measure阶段结束
// II. 窗口layout阶段
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
...
boolean hadSurface = mSurface.isValid();
try {
...
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
} catch (RemoteException e) {
}
...
// II. 窗口layout阶段结束
// III. measure阶段
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);
// 这里其实就是调用了View.measure()方法
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
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 {
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
maybeHandleWindowMove(frame);
}
if (surfaceSizeChanged) {
updateBoundsSurface();
}
// III. measure阶段结束
// IV. layout阶段开始
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
...
if (didLayout) {
// 进行layout过程
performLayout(lp, mWidth, mHeight);
// By this point all views have been sized and positioned
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
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;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after setFrame");
host.debug();
}
}
// IV. layout阶段结束
// V. draw过程开始
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) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
// V. draw过程结束
}
好吧,我承认,这是我见过的最长的方法了。只能给它分为几个重要阶段,并提炼一下,无法进行太详细的分析了。
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的候选