一个View,从无到有会有三个流程:(measure、layout、draw)。
我们都知道Android视图是由一层一层构成的层级结构,直白点说,就是父View包含子View而子View又可以包含子View。所以绘制流程是由最外层的View开始,一步一步向内传递执行。而整个过程又是递归等待的,最外层的View需要等内层所有的View执行完绘制流程才结束,因此减少布局层级,可以有效提升App性能。
什么时候进行绘制呢?
Activity中onCreate()方法在setContentView()后,View的宽高是获取不到的。同时我们知道Activity在onResume()后才完全可见,并且初次在onResume()方法中也是拿不到View的尺寸的,这样可以推算得出:View的绘制流程是在onResume()方法执行结束后才开始的。那Activity的生命周期方法背后是由谁,又何时调用的?
答:ActivityManagerService
ActivityManagerService(以下简称AMS))是Androids上层系统中最核心的服务之一,主要负责系统中四大组件的启动、切换、调度及应用程序的管理和调度等工作。
ActivityThread的main方法是应用程序的入口,main()方法里做一些初始化工作,其中包括和AMS建立起通信。
public class ActivityThread{
final ApplicationThread mAppThread = new ApplicationThread();
final Looper mLooper = Looper.myLooper();
final H mH = new H();
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
public static void main(String[] args) {
//初始化lopper
Looper.prepareMainLooper();
//初始化ActivityThread
ActivityThread thread = new ActivityThread();
//ApplicationThread和AMS建立联系
thread.attach(false);
//取消息
Looper.loop();
//loop()方法如果执行结束,未能取到消息,程序抛出异常退出。
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
ActivityThread会管理和用户打交道的Activity,应用所有的Activity都存在ActivityThread中的mActivities集合中,而ActivityThread响应AMS的号召,需要借助ApplicationThread来接受这个诏令,点进去看全是生命周期方法。接着调用attach()方法让ApplicationThread和AMS建立联系。H类就是一个Handler类,用于发送生命周期改变的消息,通知响应操作。
三个阶段
View的整个绘制流程可以分为以下三个阶段:
- measure: 判断是否需要重新计算View的大小,需要的话则计算;
- layout: 判断是否需要重新计算View的位置,需要的话则计算;
- draw: 判断是否需要重新绘制View,需要的话则重绘制。
绘制流程函数调用链:
measure流程
此阶段的目的是计算出控件树中的各个控件要显示其内容的话,需要多大尺寸。起点是ViewRootImpl的measureHierarchy()方法。
//源码如下
private
boolean
measureHierarchy(
final
View host,
final
WindowManager.LayoutParams lp,
final
Resources res,
final
int
desiredWindowWidth,
final
int
desiredWindowHeight) {
// 传入的desiredWindowXxx为窗口尺寸
int
childWidthMeasureSpec;
int
childHeightMeasureSpec;
boolean
windowSizeMayChange =
false
;
. . .
boolean
goodMeasure =
false
;
if
(!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if
(mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange =
true
;
}
}
return
windowSizeMayChange;
}
上面的代码中调用getRootMeasureSpec()方法来获取根MeasureSpec,这个根MeasureSpec代表了对decorView的宽高的约束信息。继续分析之前,我们先来简单地介绍下MeasureSpec的概念。
MeasureSpec是一个32位整数,由SpecMode和SpecSize两部分组成,其中,高2位为SpecMode,低30位为SpecSize。SpecMode为测量模式,SpecSize为相应测量模式下的测量尺寸。View(包括普通View和ViewGroup)的SpecMode由本View的LayoutParams结合父View的MeasureSpec生成。
SpecMode的取值可为以下三种:
- EXACTLY: 对子View提出了一个确切的建议尺寸(SpecSize);
- AT_MOST: 子View的大小不得超过SpecSize;
- UNSPECIFIED: 对子View的尺寸不作限制,通常用于系统内部。
传入performMeasure()方法的MeasureSpec的SpecMode为EXACTLY,SpecSize为窗口尺寸。
//performMeasure()方法的源码如下
private
void
performMeasure(
int
childWidthMeasureSpec,
int
childHeightMeasureSpec) {
. . .
try
{
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
finally
{
. . .
}
}
上面代码中的mView即为decorView,也就是说会转向对View.measure()方法的调用。
//源码如下
/**
* 调用这个方法来算出一个View应该为多大。参数为父View对其宽高的约束信息。
* 实际的测量工作在onMeasure()方法中进行
*/
public
final
void
measure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
. . .
// 判断是否需要重新布局
// 若mPrivateFlags中包含PFLAG_FORCE_LAYOUT标记,则强制重新布局
// 比如调用View.requestLayout()会在mPrivateFlags中加入此标记
final
boolean
forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final
boolean
specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final
boolean
isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final
boolean
matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final
boolean
needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
// 需要重新布局
if
(forceLayout || needsLayout) {
. . .
// 先尝试从缓从中获取,若forceLayout为true或是缓存中不存在或是
// 忽略缓存,则调用onMeasure()重新进行测量工作
int
cacheIndex = forceLayout ? -
1
: mMeasureCache.indexOfKey(key);
if
(cacheIndex <
0
|| sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
. . .
}
else
{
// 缓存命中,直接从缓存中取值即可,不必再测量
long
value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((
int
) (value >>
32
), (
int
) value);
. . .
}
. . .
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((
long
) mMeasuredWidth) <<
32
|
(
long
) mMeasuredHeight & 0xffffffffL);
// suppress sign extension
}
从measure()方法的源码中我们可以知道,只有以下两种情况之一,才会进行实际的测量工作:
- forceLayout为true:这表示强制重新布局,可以通过View.requestLayout()来实现;
- needsLayout为true,这需要specChanged为true(表示本次传入的MeasureSpec与上次传入的不同),并且以下三个条件之一成立:
- sAlwaysRemeasureExactly为true: 该变量默认为false;
- isSpecExactly为false: 若父View对子View提出了精确的宽高约束,则该变量为true,否则为false
- matchesSpecSize为false: 表示父View的宽高尺寸要求与上次测量的结果不同
对于decorView来说,实际执行测量工作的是FrameLayout的onMeasure()方法。
//源码如下
@Override
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
int
count = getChildCount();
. . .
int
maxHeight =
0
;
int
maxWidth =
0
;
int
childState =
0
;
for
(
int
i =
0
; i < count; i++) {
final
View child = getChildAt(i);
if
(mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec,
0
, heightMeasureSpec,
0
);
final
LayoutParams lp = (LayoutParams) child.getLayoutParams();
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());
. . .
}
}
// 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());
// Check against our foreground's minimum height and width
final
Drawable drawable = getForeground();
if
(drawable !=
null
) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
. . .
}
FrameLayout是ViewGroup的子类,后者有一个View[]类型的成员变量mChildren,代表了其子View集合。通过getChildAt(i)能获取指定索引处的子View,通过getChildCount()可以获得子View的总数。
在上面的源码中,首先调用measureChildWithMargins()方法对所有子View进行了一遍测量,并计算出所有子View的最大宽度和最大高度。而后将得到的最大高度和宽度加上padding,这里的padding包括了父View的padding和前景区域的padding。然后会检查是否设置了最小宽高,并与其比较,将两者中较大的设为最终的最大宽高。最后,若设置了前景图像,我们还要检查前景图像的最小宽高。
经过了以上一系列步骤后,我们就得到了maxHeight和maxWidth的最终值,表示当前容器View用这个尺寸就能够正常显示其所有子View(同时考虑了padding和margin)。而后我们需要调用resolveSizeAndState()方法来结合传来的MeasureSpec来获取最终的测量宽高,并保存到mMeasuredWidth与mMeasuredHeight成员变量中。
从以上代码的执行流程中,我们可以看到,容器View通过measureChildWithMargins()方法对所有子View进行测量后,才能得到自身的测量结果。也就是说,对于ViewGroup及其子类来说,要先完成子View的测量,再进行自身的测量(考虑进padding等)。
ViewGroup的measureChildWithMargins()方法的实现:
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);
}
从上述代码可知,对于ViewGroup来说,它会调用child.measure()来完成子View的测量。传入ViewGroup的MeasureSpec是它的父View用于约束其测量的,那么ViewGroup本身也需要生成一个childMeasureSpec来限制它的子View的测量工作。这个childMeasureSpec就由getChildMeasureSpec()方法生成。
public
static
int
getChildMeasureSpec(
int
spec,
int
padding,
int
childDimension) {
// spec为父View的MeasureSpec
// padding为父View在相应方向的已用尺寸加上父View的padding和子View的margin
// childDimension为子View的LayoutParams的值
int
specMode = MeasureSpec.getMode(spec);
int
specSize = MeasureSpec.getSize(spec);
// 现在size的值为父View相应方向上的可用大小
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
) {
// 表示子View的LayoutParams指定了具体大小值(xx dp)
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
else
if
(childDimension == LayoutParams.MATCH_PARENT) {
// 子View想和父View一样大
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
else
if
(childDimension == LayoutParams.WRAP_CONTENT) {
// 子View想自己决定其尺寸,但不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break
;
// Parent has imposed a maximum size on us
case
MeasureSpec.AT_MOST:
if
(childDimension >=
0
) {
// 子View指定了具体大小
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
else
if
(childDimension == LayoutParams.MATCH_PARENT) {
// 子View想跟父View一样大,但是父View的大小未固定下来
// 所以指定约束子View不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
else
if
(childDimension == LayoutParams.WRAP_CONTENT) {
// 子View想要自己决定尺寸,但不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break
;
. . .
}
//noinspection ResourceType
return
MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
上面的方法展现了根据父View的MeasureSpec和子View的LayoutParams生成子View的MeasureSpec的过程, 子View的LayoutParams表示了子View的期待大小。这个产生的MeasureSpec用于指导子View自身的测量结果的确定。
在上面的代码中,我们可以看到当ParentMeasureSpec的SpecMode为EXACTLY时,表示父View对子View指定了确切的宽高限制。此时根据子View的LayoutParams的不同,分以下三种情况:
- 具体大小(childDimension):这种情况下令子View的SpecSize为childDimension,即子View在LayoutParams指定的具体大小值;令子View的SpecMode为EXACTLY,即这种情况下若该子View为容器View,它也有能力给其子View指定确切的宽高限制(子View只能在这个宽高范围内),若为普通View,它的最终测量大小就为childDimension。
- match_parent:此时表示子View想和父View一样大。这种情况下得到的子View的SpecMode与上种情况相同,只不过SpecSize为size,即父View的剩余可用大小。
- wrap_content: 这表示了子View想自己决定自己的尺寸(根据其内容的大小动态决定)。这种情况下子View的确切测量大小只能在其本身的onMeasure()方法中计算得出,父View此时无从知晓。所以暂时将子View的SpecSize设为size(父View的剩余大小);令子View的SpecMode为AT_MOST,表示了若子View为ViewGroup,它没有能力给其子View指定确切的宽高限制,毕竟它本身的测量宽高还悬而未定。
当ParentMeasureSpec的SpecMode为AT_MOST时,我们也可以根据子View的LayoutParams的不同来分三种情况讨论:
- 具体大小:这时令子View的SpecSize为childDimension,SpecMode为EXACTLY。
- match_parent:表示子View想和父View一样大,故令子View的SpecSize为size,但是由于父View本身的测量宽高还无从确定,所以只是暂时令子View的测量结果为父View目前的可用大小。这时令子View的SpecMode为AT_MOST。
- wrap_content:表示子View想自己决定大小(根据其内容动态确定)。然而这时父View还无法确定其自身的测量宽高,所以暂时令子View的SpecSize为size,SpecMode为AT_MOST。
从上面的分析我们可以得到一个通用的结论,当子View的测量结果能够确定时,子View的SpecMode就为EXACTLY;当子View的测量结果还不能确定(只是暂时设为某个值)时,子View的SpecMode为AT_MOST。
在measureChildWithMargins()方法中,获取了知道子View测量的MeasureSpec后,接下来就要调用child.measure()方法,并把获取到的childMeasureSpec传入。这时便又会调用onMeasure()方法,若此时的子View为ViewGroup的子类,便会调用相应容器类的onMeasure()方法,其他容器View的onMeasure()方法与FrameLayout的onMeasure()方法执行过程相似。
回到FrameLayout的onMeasure()方法,当递归地执行完所有子View的测量工作后,会调用resolveSizeAndState()方法来根据之前的测量结果确定最终对FrameLayout的测量结果并存储起来。View类的resolveSizeAndState()方法的源码如下:
public
static
int
resolveSizeAndState(
int
size,
int
measureSpec,
int
childMeasuredState) {
final
int
specMode = MeasureSpec.getMode(measureSpec);
final
int
specSize = MeasureSpec.getSize(measureSpec);
final
int
result;
switch
(specMode) {
case
MeasureSpec.AT_MOST:
if
(specSize < size) {
// 父View给定的最大尺寸小于完全显示内容所需尺寸
// 则在测量结果上加上MEASURED_STATE_TOO_SMALL
result = specSize | MEASURED_STATE_TOO_SMALL;
}
else
{
result = size;
}
break
;
case
MeasureSpec.EXACTLY:
// 若specMode为EXACTLY,则不考虑size,result直接赋值为specSize
result = specSize;
break
;
case
MeasureSpec.UNSPECIFIED:
default
:
result = size;
}
return
result | (childMeasuredState & MEASURED_STATE_MASK);
}
对于普通View,会调用View类的onMeasure()方法来进行实际的测量工作,该方法的源码如下:
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
对于普通View(非ViewgGroup)来说,只需完成自身的测量工作即可。以上代码中通过setMeasuredDimension()方法设置测量的结果,具体来说是以getDefaultSize()方法的返回值来作为测量结果。getDefaultSize()方法的源码如下:
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;
}
从上述代码可以看出,View的getDefaultSize()方法对于AT_MOST和EXACTLY这两种情况都返回了SpecSize作为result。所以若我们的自定义View直接继承了View类,我们就要自己对wrap_content (对应了AT_MOST)这种情况进行处理,否则对自定义View指定wrap_content就和match_parent效果一样了。
layout流程
通过layout流程来确定子View在父View中的位置。子View在父View中的位置,可以通过四个点来确定,同时也可以通过点的距离来计算出View的大小。
performLayout方法中会执行DecorView的layout()方法来开始整个View树的layout流程。而DecorView包括其他的ViewGroup都没有另外实现layout()方法,都会执行到View的layout()方法。layout()方法中会先执行setFrme()方法确定View自己在父View中的位置,接着再执行onLayout()方法来遍历所有的子View,计算出子View在自己心中的位置(4个点)后,再执行子View的layout流程。不同的ViewGroup有着不同的方式来安排子View在自己心中的位置。所以View类中的onLayout()是一个空方法,等着View们自己去实现。自定义ViewGroup的时候如果不在onLayout方法中安排子View的位置,将看不见子View。
layout流程相对于ViewGroup而言:
1、确定自己在父View中的位置。
2、遍历所有子View,计算出位置后,再执行子View的layout流程。
draw流程
performDraw()方法会通过层层调用执行到View的draw()方法。
private void performDraw() {
draw(fullRedrawNeeded);
}
private void draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
mView.draw(canvas);
}
public void draw(Canvas canvas) {
//绘制自己的背景
drawBackground(canvas);
//空实现,绘制自己的内容,自定义时重写该方法
onDraw(canvas)
//绘制子View
dispatchDraw(canvas);
//绘制前景
onDrawForeground(canvas);
}
draw()方法会绘制一些自己的东西,通过dispatchDraw()方法来传递执行子View的draw流程。
在ViewGroup类中已经实现:
protected void dispatchDraw(Canvas canvas) {
more |= drawChild(canvas, child, drawingTime);
}