View何时开始绘制?-requestLayout()
- Window是View的载体,在ViewRootImpl的setView方法中添加Window到WMS之前。会调用requestLayout绘制整颗View Hierarchy的绘制。
- Activity在onResume执行结束后才会进行view的绘制和显现。在handleResumeAcitvity()方法执行完Activity的onResume()方法后,会把DecorView加载到Window中,并同时创建ViewRootImpl对象。
ActivityThread::handleResumeActivity()
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
//...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//调用WindowManger的addView()方法
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
经过一系列的调用之后
WinWindowManagerImpl ::addView() --> WindowManagerGlobal::addView() -->ViewRootImpl::setView()
看看ViewRootImp::setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//...
//进行View的绘制流程
requestLayout();
//...
//通过session与WMS建立通信
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
//...
}
接下来看看ViewRootImpl::requestLayout():
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查是否在主线程,在子线程会抛出异常
checkThread();
//是否measure和layout布局的开关
mLayoutRequested = true;
//开始遍历View Hierarchy绘制
scheduleTraversals();
}
}
为什么不能在子线程更新UI?
Android中的UI访问时没有加锁,多个线程可以同时访问更新操作同一个UI控件,是线程不安全的。在多线程模式下,当多个线程共同访问更新操作同一个UI控件时,容易产生不可控的错误,这是致命的。
接下来继续看看ViewRootImpl::scheduleTraversals() 方法
void scheduleTraversals() {
if (!mTraversalScheduled) {//防止同一帧绘制多次
mTraversalScheduled = true;
//同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//向主线程消息队列post一个Runnable.这个Runnable最终会调用performTraversals()
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//...
}
}
- 向消息队列添加同步屏障,它的作用是非异步的消息都不能执行,保障刷新的Message能够第一时间得到调用。
View树绘制的起点 - performTraversals()
performTraversals()它是整个View Hierarchy绘制的起点,它里面会执行View绘制的三大工作流程,进入ViewRootImpl::performTraversals()瞅瞅:
private void performTraversals() {
//代表着View Hierarchy的根节点,即根视图
final View host = mView;
//...
WindowManager.LayoutParams lp = mWindowAttributes;
//desiredWindowWidth和desiredWindowHeight分别代表着屏幕的宽度和高度
int desiredWindowWidth;
int desiredWindowHeight;
//...
if (mLayoutRequested) {
final Resources res = mView.getContext().getResources();
//...
//这里调用了measureHierarchy方法,里面会调用performMeasure方法,执行View Hierarchy的measure流程
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
//...
}
//...
if(didLayout){
//这里调用了performLayout方法,执行View Hierarchy的layout流程
performLayout(lp, mWidth, mHeight);
//...
}
//...
if (!cancelDraw && !newSurface) {
//...
//这里调用了performDraw方法,执行View Hierarchy的draw流程
performDraw();
}
//...
}
看看ViewRootImpl::measureHierarchy():
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
//...
//顶级View在调用performMeasure方法之前,会先调用getRootMeasureSpec方法来生成自身宽和高的MeasureSpec
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//这里调用performMeasure方法,执行View Hierarchy的measure流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
View的绘制的三个方法:
- onMeasure:测量,判断当前这个View需要多大
- onLayout:布局,判断当前这个View要放置到整个布局的什么地方
- onDraw:绘制
View的测量流程 – performMeasure()
首先先看看ViewRootImpl::measureHierarchy()方法中的ViewRootImpl::getRootMeasureSpec()方法:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// 如果为MATCH_PARENT,MeasureSpecMode为MeasureSpec.EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 如果为WRAP_CONTENT,MeasureSpecMode为MeasureSpec.EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 如果有固定的值,MeasureSpecMode也是MeasureSpec.EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
顶级View和子View的MeasureSpec
接下来看看上面所示源码中出现最多的MeasureSpec,它是View的静态内部类。
进入源码View::MeasureSpec类(顶级View):
public static class MeasureSpec {
//左移位数
private static final int MODE_SHIFT = 30;
//位掩码
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//...
@UnsupportedAppUsage
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
//从给定的MeasureSpec中取出SpecMode
@MeasureSpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//从给定的MeasureSpec中取出SpecSize
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
//...
}
}
从上面可以看出,。MeasureSpec是一个32位的int值,高两位表示SpecMode测量模式,低30位表示当前测量模式下的规格大小。SpecMode可以有三种取值:
- UNSPECIFIED:父控件没有给子view任何限制,子View可以设置为任意大小。在ScrollView这类滑动控件的情况下,子视图的高度是不相关的。 因此,它将向孩子提供一个UNSPECIFIED视图,告诉孩子他们可以像他们需要的一样高。ScrollView将处理它们的绘图和放置。
- EXACTLY :精准模式,设置空间大小时如果是指定大小或者match-parent就都是这种模式。
- AT_MOST:最大模式,wrapContent就是这种。
MeasureSpec讲解完之后,又回到最初的ViewRootImpl::getRootMeasureSpec()方法,此方法为顶级View的MeasureSpec的创建,由于它没有父容器,所以MeasureSpec是由屏幕窗口和自身的LayoutParams来共同决定的,rootDimension就是传入的屏幕窗口的LayoutParams的大小模式。
接下来看看子View的MeasureSpec创建 --getChildMeasureSpec()
子View的MeasureSpec都是由父容器的MeasureSpec和自身的LayoutParams共同决定的。
ViewGroup::measureChild()
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//这里调用了getChildMeasureSpec方法,里面就是创建子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
//如果子View是一个ViewGroup,递归measure下去
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在子View进行measure()方法前会调用getChildMeasureSpec()获得子View的MeasureSpec,我们进入ViewGroup::getChildMeasureSpec()方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//取出父容器的测量模式specMode
int specMode = MeasureSpec.getMode(spec);
//取出父容器的测量大小specSize
int specSize = MeasureSpec.getSize(spec);
//子View最大可用大小size == 父容器剩余大小 == 父容器的尺寸减去padding
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://父容器是EXACTLY
if (childDimension >= 0) {//子View的LayoutParams是固定大小
//子View的大小
resultSize = childDimension;
//子View的MeasureSpecMode
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//子View的LayoutParams是MATCH_PARENT
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {如果子View的LayoutParams是WRAP_CONTENT
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST://父容器是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://父容器是UNSPECIFIED
if (childDimension >= 0) {
// Child wants a specific size... let him 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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : 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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
看一下View.sUseZeroUnspecifiedMeasureSpec:
/**
* Always return a size of 0 for MeasureSpec values with a mode of UNSPECIFIED
*/
static boolean sUseZeroUnspecifiedMeasureSpec = false;
看完顶级View和子View的MeasureSpec后,接下来就进入measure流程了
View和ViewGroup的measure流程
measure流程都是从ViewRootImpl的performMeasure()开始,并且都会先调用View的measure方法。
ViewRootImpl::performMeasure():
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
View::measure():
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//...
onMeasure(widthMeasureSpec, heightMeasureSpec);
//...
}
measure方法是一个final方法,方法不能被子类重写,measure的具体过程交给了onMeasure方法实现。
View的onMeasure流程
进入View::onMeasure():
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//如果View没有重写onMeasure方法,则会调用setMeasuredDimension方法设置宽高,在设置之前先调用getDefaultSize方法获取默认宽高
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
当调用setMeasuredDimension方法设置View的宽高后,就可以通过getMeasureWidth()或getMeasureHeight()获得View测量的宽高,看看 getDefaultSize()方法:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//如果specMode是UNSPECIFIED,返回的大小就是传进来的size,而这个size就是通过getSuggestedMinimumWidth()或getSuggestedMinimumHeight()方法获得的
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//如果specMode是AT_MOST或EXACTLY,返回的大小就是MeasureSpec中的specSize
//如果自定义控件直接继承自View,得重写onMeasure()方法,因为在AT_MOST
//或者EXACTALY,它们的返回值一样,都是父容器剩下的大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
在MeasureSpec.UNSPECIFIED下,看看getSuggestedMinimumWidth()得到的大小:
protected int getSuggestedMinimumHeight() {
//在UNSPECIFIED模式下,如果View没有设置背景,
//那么View的高就等于android:minHeight,如果View设置了背景,
//那么View的宽就等于View的背景background的宽和android:minHeight的最大值.。
return (mBackground == null) ?
mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
View的onMeasure方法执行完后,就可以通过getMeasureWidth()或getMeasureHeight()获得View测量的宽高,但是有可能会不准确。
ViewGroup的Measure流程
ViewGroup继承自View,但是是一个抽象类,并没有重写View的onMeasure()方法,而是由ViewGroup的子类重写onMeasure()方法,实现不同的measure流程,以FrameLayout为例。
FrameLayout::onMeasure():
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取子View的个数
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//遍历所有子View,测量每个子View的大小
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {//如果子View可见
//测量子View的大小
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//FrameLayout自身的测量宽高就是所有子View宽高中的最大值
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//...
}
}
}
FrameLayout的onMeasure()方法是遍历所有子View,然后逐个测量子View的大小
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
//省略的这部分在上面已经讲过,主要是创建子View的MeasureSpec(childWidthMeasureSpec, childHeightMeasureSpec)
//...
//调用子View的measure方法,叫子View自己测量自己
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
小结
measure流程主要是测量View的宽高。首先会去计算MeasureSpec,如果是DecorView就会根据window尺寸和自身的参数来决定,如果是普通的View,就是根据父容器和自身的参数决定。在计算好MeasureSpec后,就会调用performMeasure,里面调用measure()进行测量工作,最后调用onMeasure()去设置测量宽高,如果是ViewGroup,因为它并没有定义其测量的具体过程,测量过程需要子类实现。所以会在具体的ViewGroup中还会去遍历子View,调用子View的measure进行测量工作;如果是子View,直接开始测量,设置View的宽高,这就是整个测量流程。
参考资料:
View的工作原理