ViewRootImpl 和 DecorView
ViewRootImpl 是连接 WindowManager 和 DecorView 的纽带,View 绘制流程的 onMeasure、onLayout 和 onDraw 均通过 ViewRootImpl 完成。Activity 创建后,DecorView 会添加到 Window 中,同时会创建 ViewRootImpl 对象,将其与 DecorView 相关联。
root = new ViewRootImpl(view.getContext(), display);
/* display标识显示设备 */
root.setView(view, wparams, panelParentView);
/* WindowManager.LayoutParams wparams, Window panelParentView */
View 的绘制从 ViewRootImpl 的 performTraversals 方法开始,该方法分别依次调用 performMeasure、performLayout 和 performDraw,这 3 个方法本质完成了 mView.measure、mView.layout 和 mView.draw,mView 是关联源码中的 view,也就是程序的 DecorView。
在 measure、layout 和 draw 各自方法内部会调用 onMeasure、onLayout 和 onDraw,而在 onMeasure 等方法再次对所有子元素进行 measure 等过程,此时绘制流程就从父元素传递到子元素中了,如此反复即可完成整个 View 树的遍历绘制。
DecorView 会包含竖直方向的 LinearLayout,该布局有上方标题栏和下方内容栏两部分,Activity 中通过 setContentView 方法设置的布局文件会被加到内容栏中,内容栏的 Android id 是 content,布局类型为 FrameLayout。可以通过 ViewGroup content = findViewById(android.R.id.content) 获得 content 实例,进一步 content.getChildAt(0) 获得我们 setContentView 的布局实例。
MeasureSpec
MeasureSpec 受父容器施加规则的影响,其决定 View 的尺寸规格。MeasureSpec 标识 32 位 int 值,高 2 位表示测量模式 SpecMode,低 30 位表示测量模式下的规格大小 SpecSize。
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/* 3 种测量模式 */
private static final int UNSPECIFIED = 0 << MODE_SHIFT; /* 00 */
private static final int EXACTLY = 1 << MODE_SHIFT; /* 01 */
private static final int AT_MOST = 2 << MODE_SHIFT; /* 10 */
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
/* MODE_MASK = 11 000000000000000000000000000000
~MODE_MASK = 00 111111111111111111111111111111
size & ~MODE_MASK将size的高2位清空,mode & MODE_MASK同理
*/
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
SpecMode 有 UNSPECIFIED、EXACTLY 和 AT_MOST 3 类:
- UNSPECIFIED:未指定大小限制,一般用于 ListView 这种可滚动容器;
- EXACTLY:父容器告诉子 View 必须遵守的明确尺寸;
- AT_MOST:父布局给子 View 的最大尺寸限制。
父容器和子 View 的 LayoutParams 共同决定子 View 的 MeasureSpec,进一步决定 View 的宽高。例外是 DecorView,其 MeasureSpec 由 Window 尺寸和自身的 LayoutParams 确定,代码块的 getRootMeasureSpec 方法得知,DecorView 根据它的 LayoutParams 的宽高参数来划分:match_parent 对应 EXACTLY 模式,大小就是窗口大小、wrap_content 对应 WRAP_CONTENT 模式,大小不能超过窗口大小;硬解码尺寸对应 EXACTLY 模式,大小为 LayoutParams 指定的大小。
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
int measureSpec;
final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
? MATCH_PARENT : measurement;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
对普通 View 来说,其 measure 过程由 ViewGroup 的 measureChildWithMargins 和 getChildMeasureSpec 方法完成。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/* mPaddingLeft等属性是父容器的padding */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let them have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
| 子尺寸 & 父模式 | EXACTLY | AT_MOST | UNSPECIFIED |
|---|---|---|---|
| 具体尺寸 | EXACTLY childSize | EXACTLY childSize | EXACTLY childSize |
| match_parent | EXACTLY parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
| wrap_content | AT_MOST parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
View 的工作流程
工作流程即前面提到的 measure 测量、layout 布局和 draw 绘制。measure 确定 View 的测量宽高、layout 确定 View 的最终宽高和 4 顶点的位置、draw 将 View 绘制到屏幕上。
measure 过程
View 的 measure 过程由 measure 方法完成,measure 方法是 final 方法,会去调用 View 的 onMeasure 方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasureDimenson(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), HeightMeasureSpec));
}
setMeasureDimenson 方法会设置 View 的宽高测量值,参数内的 getDefaultSize 方法返回的大小就是 measureSpec 的 specSize,specSize 相当于 View 测量后的大小。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
注意到 AT_MOST 和 EXACTLY 模式均返回 SpecSize,这表示基本 View 设置宽高参数为 match_parent 和 wrap_content 是相同的逻辑,如果要自定义 View,则大多数情况下都要重新 onMeasure 方法。
对于 UNSPECIFIED 情况,View 的大小为 getDefaultSize 的第一个参数 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 的返回值。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
若 View 没有设置背景,则 getSuggestedMinimumWidth 返回 mMinWidth,如果有设置背景,则 getSuggestedMinimumWidth 返回背景 Drawable 的原始尺寸 getIntrinsicWidth。
ViewGroup 的 measure 过程会另外遍历调用子元素的 measure 方法,ViewGroup 是抽象类,没有重写 View 的 onMeasure 方法,但提供了 measureChildren 方法。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
由 ViewGroup 的调用链可知,ViewGroup 没有定义测量的具体过程,onMeasure 方法要各个子类去重写。
measure 过程完成后,通过 getMeasuredWidth 和 getMeasuredHeight 方法可以获取 View 的测量尺寸,前面我们提到,Activity 创建后,DecorView 会添加到 Window 中,同时创建并关联 ViewRootImpl 对象,ViewRootImpl 对象调用 performTraversals 方法开始绘制 DecorView 以及整个 View 树,其具体的发生时机是 ActivityThread 内部回调 Activity 的 onResume 方法后,调用 WindowManager 的 addView 方法,内部会由 ViewRootImpl 接手执行 View 绘制逻辑。
所以 View 的 measure 过程和 Activity 的生命周期不是同步的,无法在 onCreate、onStart 和 onResume 方法获得正确的测量宽高,大多数情况下会返回 0。有 3 种方法来解决这个问题:
- Activity 和 View 的 onWindowFocusChanged 方法均在当前窗口的焦点获得情况发生变化时回调,窗口获得焦点时,View 树的绘制流程是完成的,所以可以通过重写 onWindowFocusChanged 方法来获得 View 的测量宽高。
- View 的 post 方法可以将 Runnable 发送到当前 Thread 的消息队列的尾部,因为 View 的绘制工作都会发送到主线程的消息队列按次序处理,所以不用在 Runnable 保证 View 一定完成了 measure 流程,直接在 Activity 的回调方法中操作即可。
- ViewTreeObserver 类有众多回调接口,使用 OnGlobalLayoutListener 接口会在 View 树的状态发生改变或 View 树内部的 View 的 Visibility 发生改变时回调内部的 onGlobalLayout 方法,也就是在 View 树完成 layout 流程后,接下来的 draw 流程前回调。
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deorecation")
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
})
}
layout 过程
layout 是 ViewGroup 用来确定子元素的位置的流程,当 ViewGroup 的位置确定后, 其在 onLayout 会遍历所有子元素且调用子元素的 layout 的方法。
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_MEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_MEEDED_BEFORE_LAYOUT;
} /* View是否重新测量的相关决策 */
int oldL = mLeft;
int oldT = mTop;
int oldR = mRight;
int oldB = mBottom;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null & li.mOnLayoutChangeListeners != null {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
layout 方法会首先通过 setFrame 方法设定 View 4 顶点的位置,初始化 mLeft、mTop、mRight 和 mBottom 4 个值,View 的各顶点确定,其在父容器的位置也确定,接下来会调用 onLayout 方法,和 onMeasure 相似的,onLayout 方法在 ViewGroup 同样没有具体实现,要各子类重写。
draw 过程
draw 过程会经历 4 步:绘制背景 background.draw(canvas)、绘制自己 onDraw、绘制 children 的 dispatchDraw 和绘制装饰 onDrawScrollBars。View 绘制过程的传递通过 dispatchDraw 方法实现的,dispatchDraw 会遍历调用所有子元素的 draw 方法,所以 View 3 种工作流程的 View 树传递机制都是类似的
@CallSuper
public void draw(@NonNull Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
* 7. If necessary, draw the default focus highlight
*/
// Step 1, draw the background, if needed
int saveCount;
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}