Android浅析之View的绘制流程

今天咱们来聊聊Android的绘制流程,从大方向讲,View是在何处开始被绘制的?从具体步骤看,View的具体绘制流程是咋样的?

一、View绘制的入口在哪里?

从用户点击APP开始,会经历加载启动应用程序-显示空白窗口-创建应用进程-创建应用主线程-创建启动Activity-加载测量布局绘制,我们都知道Activity是在OnCreate函数去setContentView,但是此时界面并没有完成绘制,只是把我们的contentView add到window的decorView里面id为content的FrameLayout中,然后用Android的XmlPull解析器将xml布局节点解析出来,通过反射去实例View,这只是一个加载解析的过程,而在Activity的OnResume函数之后界面才算绘制完成,那界面的绘制入口逻辑会不会就在OnResume函数之前呢?通过源码分析一波,从ActivityThread的handleResumeActivity函数入手:

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
        ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            // Normally the ViewRoot sets up callbacks with the Activity
            // in addView->ViewRootImpl#setView. If we are instead reusing
            // the decor view we have to notify the view root that the
            // callbacks may have changed.
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;           
                //重点在这
                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);
            }
          }
        }

这里把decorView又添加到了windowManager中,实际调用在WindowManagerImpl


public final class WindowManagerImpl implements WindowManager {

@UnsupportedAppUsage
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
}

再跟进WindowManagerGlobal

public final class WindowManagerGlobal {

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ...省略代码
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
           
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                // 将decorView及其布局参数又传入了viewRootImpl
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

}

看看ViewRootImpl的setView()做了什么?

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
        ...
        requestLayout();
        ...
    }

关键步骤requestLayout(),此函数会依次触发scheduleTraversals()-> doTraversal() -> performTraversals() -> performMeasure() -> performLayout() -> performDraw() 至此,触发流程分析结束,接下来看View内部的具体流程。

二、View的测量、布局、绘制

测量

从View的绘制入口我们可以得知handleResumeActivity会触发requestLayout()去进行decorView测量布局绘制,接下来我们根据源码先看下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);
    }
}

这里的mView实际就是DecorView,从这里开始就会进行测量,childWidthMeasureSpec和childHeightMeasureSpec就是window宽高的测量规格,也就是测量模式和测量大小

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
        // measure ourselves, this should set the measured dimension flag back
        // 这里会走decorView自身的onMeasure()函数
        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
        setMeasuredDimensionRaw((int) (value >> 32), (int) value);
        mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    ...

}

DecorView本身是一个frameLayout,我们看下它内部的onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    // 父布局的宽高测量模式是否不为EXACTLY
    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();

    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) {
            //在这里,如果子view是显示的话,就会进行第一次测量
            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());
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                     //当父布局不为EXACTLY将宽高为Match_Parent子布局的添加到集合中
                    mMatchParentChildren.add(child);
                }
            }
        }
    }
    ...
    count = mMatchParentChildren.size();
    // 当Match_Parent的子布局超过1个时
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            final int childWidthMeasureSpec;
            if (lp.width == LayoutParams.MATCH_PARENT) {
                final int width = Math.max(0, getMeasuredWidth()
                        - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                        - lp.leftMargin - lp.rightMargin);
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        width, MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                        lp.leftMargin + lp.rightMargin,
                        lp.width);
            }

            final int childHeightMeasureSpec;
            if (lp.height == LayoutParams.MATCH_PARENT) {
                final int height = Math.max(0, getMeasuredHeight()
                        - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                        - lp.topMargin - lp.bottomMargin);
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        height, MeasureSpec.EXACTLY);
            } else {
                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                        lp.topMargin + lp.bottomMargin,
                        lp.height);
            }
            // 将mMatchParentChildren中的子布局再次测量
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

大家在写自定义View时,可能也会发现自己写的View onMeasure函数有时会被回调多次,从上面这个函数中可以看出一些端倪,当满足三个条件:

  • 1.父布局宽高测量模式不为EXACTLY
  • 2.子布局属性为MatchParent
  • 3.父布局中子布局属性为MatchParent大于1个时

满足这些条件,子View就会被多次测量。

measureChildWithMargins会根据父布局的MeasureSpec、边距、子布局的宽高计算出子布局的childWidthMeasureSpec,并传入到子View的measure函数中,后续也会传入到子View的onMeasure

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);
}

详细看看父布局是如何进行子布局的MeasureSpec的计算

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
    // 父布局测量模式为EXACTLY
    case MeasureSpec.EXACTLY:
        //子布局设置了布局大小dp/px
        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 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);
}

总结一下:

image.png

布局

layout与measure类似,由ViewRootImpl的performLayout()触发,执行View的Layout函数,先看下View中layout实现

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) ?
            // setFrame(l, t, r, b)确定自身的左上右下
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // 在View中是空实现,ViewGroup需要重写此函数确定子View的布局
        onLayout(changed, l, t, r, b);

        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }

        ...
    }

}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}


void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
            ...
            //确定子View左上右下的位置
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

需要注意的是,在measure测量完成时,其实并不能算是确定了View的宽高,在进行View的layout过程中也会涉及View的位置、宽高的设置,而measure在前,layout在后,最终的还是以layout设置的左上右下的值为View的最终大小,measure的流程那么复杂,居然最后还要看layout的脸色。

绘制


private void performDraw() {
    ...
    draw(fullRefrawNeeded);
    ...
}

private void draw(boolean fullRedrawNeeded) {
    ...
    if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset,
            scalingRequired, dirty)) {
        return;
    }
    ...
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo,
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
    ...
    mView.draw(canvas);
    ...
}
// 绘制基本上可以分为六个步骤
public void draw(Canvas canvas) {
        ...
   // 步骤一:绘制View的背景
   drawBackground(canvas);

   ...
   // 步骤二:如果需要的话,保持canvas的图层,为fading做准备
   saveCount = canvas.getSaveCount();
   ...
   canvas.saveLayer(left, top, right, top + length, null, flags);

   ...
   // 步骤三:绘制View的内容
   onDraw(canvas);

   ...
   // 步骤四:绘制View的子View
   dispatchDraw(canvas);

   ...
   // 步骤五:如果需要的话,绘制View的fading边缘并恢复图层
   canvas.drawRect(left, top, right, top + length, p);
   ...
   canvas.restoreToCount(saveCount);

   ...
   // 步骤六:绘制View的装饰(例如滚动条等等)
   onDrawForeground(canvas)
 }

到这里,View的绘制流程就分析结束了,如果觉得有所帮助,欢迎点赞留言。

猜你喜欢

转载自juejin.im/post/7112707995041005582