View的绘制流程是Android GUI系统中的关键部分,因为最终view中绘制的内容是要呈现给用户的。本篇基于Android4.4(KitKat)将对view绘制流程做一个全面的分析。
一、绘制缓冲区
在View绘制流程中首先是需要一块缓冲区提供给应用程序进行内容绘制的, 这个缓冲区在上层以Surface的形式提供给使用者,这个Surface就是view绘制流程的绘图表面。在<Surface绘图缓冲区的创建流程> 一篇中我们介绍了Surface绘图缓冲区的绘制。所以关于这部分的内容不再做介绍。
二、绘制的时机
在<Activity启动分析(二)>一篇中,我们知道在handleResumeActivity通过addView添加view,将window加入到WMS后,会通过updateViewLayout更新视图
frameworks/base/core/java/android/view/WindowManagerGlobal.java
//更新视图
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);//设置view布局参数
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);//找到对应的viewroot
mParams.remove(index);//移除之前的params
mParams.add(index, wparams);//添加新的params
root.setLayoutParams(wparams, false);//通过root设置参数 false代表view已经添加过 这里会对view树重新绘制
}
}
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
……
scheduleTraversals();//设置完成后就准备绘制
}
}
在root.setLayoutParams中会通过scheduleTraversals()绘制view。view树的绘制流程是在ViewRootImpl中完成的。
三、绘制流程
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//请求同步信号,信号到来时会调用mTraversalRunnable执行doTravels
scheduleConsumeBatchedInput();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
……
try {
performTraversals();//真正的绘制流程是从这里开始的
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
……
}
}
绘制流程需要配合Vsync信号来进行,这个是通过Choreographer来进行的,在scheduleTraversals中通过mChoreographer请求同步信号, 信号到来时会调用mTraversalRunnable的doTraversal。在doTraversal中调用performTraversals来开始真正的绘制流程。
private void performTraversals() {
……
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
……
if (!mStopped) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//获得view宽高的测量规格,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//开始执行测量操作
……
}
}
……
final boolean didLayout = layoutRequested && !mStopped;
boolean triggerGlobalLayoutListener = didLayout
|| attachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
……
}
……
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {//既没有取消绘制,也没有创建新的平面
if (!skipDraw || mReportNextDraw) {//
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();//开始执行绘制操作
}
}
……
}
在performTraversals中会做很多事情,这里我们主要绘制的主要流程,即measure,layout和draw的过程,在<Surface绘图缓冲区的创建流程>一篇中我们知道了在绘制之前是需要准备画布的,这个画布就是ViewRootImpl的mSurface,它会通过relayoutWindow创建。
3.1 measure过程
measure过程主要进行view树中所有的view的大小的测量,我们看到测量并不一定会在performTraversals中进行,而是需要满足一定的条件:
- Window的状态不能为stopped,即mStopped=false
- 窗口的触摸模式发生了变化,由此引发了Activity窗口当前获焦点的控件发生了变化,即变量focusChangedDueToTouchMode的值等于true。这个检查是通过调用ensureTouchModeLocally来实现的。
- 窗口前面测量出来的宽度host.mMeasuredWidth和高度host.mMeasuredHeight不等于WindowManagerService服务计算出来的宽度mWidth和高度mHeight。这里的host为DecorView
- 窗口的内容区域边衬大小和可见区域边衬大小发生了变化, 即contentInsetsChanged的值等于true
只有满足上述条件,measure流程才会进行,在执行measure前,首先会根据DecorView的宽高获取测量规格MesaureSpec,它的前两位为mode,有三种,分别为:
- UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大
- EXACTLY: 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。
- AT_MOST:子容器可以是声明大小内的任意大小
这个测量规格会传递给子view,子view结合自身LayoutParams算出view的大小。子view测量完成后,通过setMeasureDimentions将测量结果保存。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
在performMeasure之前会通过getRootMeasureSpec获取根view的MeasureSpec,它默认为屏幕的大小, 这里的mView是DecorView,它是一个FrameLayout,通过它我们来看看测量过程是如何进行的。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
……
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimension((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
……
}
measure方法是在view中定义的,这里需要注意measure是一个final方法,在内部它通过调用onMeasure来完成实际的测量工作。如果我们需要自定义view,就需要覆盖onMeasure在其中完成view大小的测量,我们看下默认的onMeasure实现:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;//标记已经调用了setMeasuredDimension
}
onMeasure默认通过setMeasuredDimension设置mMeasuredWidth和mMeasuredHeight的值。通过这个方法完成测量过程。默认值是通过getDefaultSize计算得到的,它的第一个参数是通过getSuggestedMinimumxx获取到的,用来获取建议的最小宽高。
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
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;
}
这个建议的最小高度值由layout:minHeight 和 BackGround的最小高度决定,最小宽度值也是类似。getDefaultSize会通过父view传递给view的MeasureSpec来计算最终宽高。对于MeasureSpec.AT_MOST和MeasureSpec.EXACTLY两种最常见的模式,最终的大小是由specSize,也就是MeasureSpec决定的。而不是由建议值。
3.2 layout过程
在测量完view大小后,通过layout来确定view的位置,layout流程需满足下面的条件:
final boolean didLayout = layoutRequested && !mStopped;
- 通过requestLayout发起过layout请求
- Window的状态不为stopped
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
同样是从view根部开始,我们看看DecorView的layout实现
frameworks/base/core/java/android/view/ViewGroup.java
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
如果LayoutTransitiond动画未执行,那么直接调用View的layout,否则设置mLayoutCalledWhileSuppressed = true,等待动画完成后再进行requestyLayout。
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
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的布局是通过onLayout来实现的,在View中onLayout的实现是空的实现,因为view是通过其父view即viewGroup进行layout的
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroup的onLayout,它是个抽象方法,所有ViewGroup的子类都需要实现此方法以完成其子view的布局。
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
我们通过以LinearLayout为例说明ViewGroup是如何进行子view的布局的。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
根据不同的oriention来调用不同的布局方法,这里我们看看layoutVertical。
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
3.3 draw过程
private void performTraversals() {
……
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {//既没有取消绘制,也没有创建新的Surface
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();//开始执行绘制操作
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
……
}
draw流程需要满足:
- 未取消绘制也没有创建新的Surface,dispatchOnPreDraw返回true或者View不可见则取消绘制
- 未跳过此次绘制,即skipDraw为false,或者mReportNextDraw为true
private void performDraw() {
……
try {
draw(fullRedrawNeeded, updateTranformHint);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
……
}
private void draw(boolean fullRedrawNeeded, boolean updateTranformHint) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
……
//重绘区域不为空或者正在执行动画
if (!dirty.isEmpty() || mIsAnimating) {
//开启了硬件加速
if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
// Draw with hardware renderer.
mIsAnimating = false;
mHardwareYOffset = yoff;
mResizeAlpha = resizeAlpha;
mCurrentDirty.set(dirty);
dirty.setEmpty();
// 硬件加速绘制
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
animating ? null : mCurrentDirty);
} else {
……
//软件绘制
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
}
}
……
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
boolean scalingRequired, Rect dirty) {
……
canvas = mSurface.lockCanvas(dirty);
mView.draw(canvas);
surface.unlockCanvasAndPost(canvas);
……
}
performDraw中会调用draw方法来完成绘制,draw方法中会通过attachInfo.mHardwareRenderer来判断是否启用了硬件加速,关于硬件加速我们在后面的篇幅进行讨论,这里我们假设未开启硬件加速关注软件绘制,即drawSoftware这个方法,这个方法首先会通过mSurface来取得绘制得画布canvas,并以dirty作为裁剪区域进行view绘制,最后通过unlockCanvasAndPost提交绘制得结果。这里mView就是我们的DecorView
frameworks/base/core/java/android/view/View.java
public void draw(Canvas canvas) {
if (mClipBounds != null) {
canvas.clipRect(mClipBounds);
}
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
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)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
final Drawable background = mBackground;
if (background != null) {
……
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}
// 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
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
……
// Step 2, save the canvas' layers
……
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
……
canvas.restoreToCount(saveCount);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
}
绘制流程的代码有点长,我做了稍微精简了下,从注释中我们可以看到绘制的具体流程分为6步:
- 绘制背景(Draw the background)
- 如果需要,为view的淡隐淡出效果保存layers(If necessary, save the canvas' layers to prepare for fading)
- 绘制view的内容(Draw view's content)
- 绘制子view(Draw children)
- 如果需要,绘制淡隐淡出效果并恢复保存的layers If necessary, draw the fading edges and restore layers
- 绘制装饰内容,比如滚动条等 Draw decorations (scrollbars for instance)
draw的第一步是进行view背景的绘制,但并不是必须的,背景资源存放在mBackground中,scrollX和scrollY都为0说明该view不用滚动,可以直接绘制其背景,否则需要进行坐标转换。一般情况下view是不带fading效果的,这时候就不需要进行第2步和第5步,否则就需要进行2到6的所有步骤。绘制view的content是通过onDraw来实现的,即真正的内容是通过子类来进行绘制的。因为view的onDraw是个空实现。绘制好自身的内容后可能该view还有子view,这时候就需要通过dispatchDraw来通知子view进行绘制,记住我们绘制的流程是从decorView开始的,它是个ViewGroup,也是整个view树的根view。我们知道ViewGroup是所有父容器的父类,所以dispatchDraw放在其中实现最合适不过了。
@Override
protected void dispatchDraw(Canvas canvas) {
final int count = mChildrenCount;
final View[] children = mChildren;
……
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
……
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
这里主要遍历父view的所有子view通过drawChild来进行绘制。在drawChild中又回到我们view的draw绘制流程,其中会通过onDraw来进行child view的内容的绘制。整个绘制流程就是这样的。