android进阶篇15、View的测量布局绘制三大流程源码解析

一、总流程解析:

1、handleResumeActivity

我们首先从ActivityThread的handleResumeActivity方法开始分析view绘制,至于什么时候调用的handleResumeActivity方法,会在启动activity的流程中调用;

handleResumeActivity方法如下所示,主要就干了两件事情;

注释1处执行performResumeActivity,在此方法中会调用到onResume方法中,所以如果我们在Resume生命周期之前获取布局中子view的宽高,会返回0,因为还没有执行addView流程;

注释2处通过wm执行addView方法,这个decor就是我们上一节介绍的DecorView;这个wm是ViewManager接口类型的,WindowManager接口继承自ViewManager;而WindowManagerImpl实现了WindowManager,所示此addView其实是调用的WindowManagerImpl的addView;

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);//1
    if (r == null) {
        // We didn't actually resume the activity, so skipping any follow-up actions.
        return;
    }
    final Activity a = r.activity;

    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;

        wm.addView(decor, l); //2
    }
}
复制代码

2、WindowManagerImpl的addView如下所示,注释1处显示调用的mGlobal的addView,这个mGlobal是WindowManagerGlobal类型的,并且是通过单例模式创建的,WindowManagerGlobal是个单例类;

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

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); //2

public static WindowManagerGlobal getInstance() {
    synchronized (WindowManagerGlobal.class) {
        if (sDefaultWindowManager == null) {
            sDefaultWindowManager = new WindowManagerGlobal();
        }
        return sDefaultWindowManager;
    }
}
复制代码

3、WindowManagerGlobal的addView如下所示,注释1处创建ViewRootImpl对象,注释2处调用了ViewRootImpl的setView方法,这个view参数仍然是包含我们布局的DecorView;

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;        
    ViewRootImpl root;
    synchronized (mLock) {
        
        root = new ViewRootImpl(view.getContext(), display); //1
        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 {
            root.setView(view, wparams, panelParentView, userId); //2
        }
    }
}
复制代码

4、在ViewRootImpl的setView方法中会调用requestLayout方法,而requestLayout方法又会调用scheduleTraversals方法,scheduleTraversals又会调用doTraversal,doTraversal又会调用performTraversals方法,这个方法应该会比较熟悉了,在这个方法中才真正开始执行view的测量布局绘制的流程;

5、performTraversals方法特别长,但其实总结起来主要就干了三件事,测量、布局和绘制,如下所示;

注释1处将mView赋值给host,这个mView在ViewRootImpl的setView方法中被赋值为docorview,所以这个host就是包含我们布局的decorView;

注释2处执行的performMeasure代表测量流程;

注释3处的performLayout代表布局流程;

注释4处的performDraw代表绘制流程;

private void performTraversals() {
    // cache mView since it is used so much below...
    final View host = mView; //1

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
     // Ask host how big it wants to be
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //2

    performLayout(lp, mWidth, mHeight); //3

    performDraw(); //4
    
}
复制代码

二、测量流程measure

1、performMeasure

测量流程是从performMeasure方法开始的,如下所示,注释1表示mView为空则直接返回,这个mView我们在setView方法中赋过值,其实就是包含我们布局的DecorView;DecorView、DecorView的父类FrameLayout、FrameLayout的父类ViewGroup都没有measure方法,因此注释2处的measure方法其实是调用的View的measure方法;

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return; //1
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //2
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
复制代码

2、View的measure方法

measure方法其实就干了一件事情,调用了onMeasure,DecorView重写了onMeasure方法,因此是调用的DecorView的onMeasure方法;在DecorView的onMeasure方法中又调用了super.onMeasure(widthMeasureSpec, heightMeasureSpec),也就是FrameLayout的onMeasure方法;

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    、、、
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    、、、
}
复制代码

3、FrameLayout的onMeasure方法

注释1处先求出子view个数;

在注释2处表示循环遍历每一个子view然后执行measureChildWithMargins;

测量完所有子view之后在注释3处设置自身的尺寸;

注释4和注释5表示会对所有设置MatchParent属性的子view重新measure,因为MatchParent属性比较特殊,刚开始并不知道父view的尺寸,所以需要重新测量;

我们继续看一下注释2处是怎么测量子view的,FrameLayout并没有measureChildWithMargins方法,而是在父类ViewGroup中定义的,我们接着看一下ViewGroup的measureChildWithMargins方法;

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount(); //1

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            measureChildWithMargins(child, widthMeasureSpec, 0,heightMeasureSpec,0); //2
        }
    }

    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT)); //3

    count = mMatchParentChildren.size(); //4
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            、、、
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //5
        }
    }
}
复制代码

4、ViewGroup的measureChildWithMargins方法

注释1处先求出子view的LayoutParams布局参数;

然后在注释2和注释3处通过父view的MeasureSpec和子view的布局参数确定子view的MeasureSpec;

最后在注释4处又调用了child.measure(childWidthMeasureSpec, childHeightMeasureSpec);这样又会递归调用到View的measure方法,在measure方法中又会调用onMeasure方法,如果这个子view是一个viewgroup类型的,则又会递归调用到该viewgroup的onMeasure方法;直到递归调用到最下层view时,则会调用到View的onMeasure方法;

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //1

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width); //2
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height); //3

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //4
}
复制代码

5、View的onMeasure方法

注释1处的setMeasuredDimension方法设置自身的尺寸,通过getDefaultSize获取尺寸;

注释3和注释4将mMeasuredWidth和mMeasuredHeight赋值为我们测量后的值;因此measure流程结束我们就可以通过getMeasuredWidth和hgetMeasuredeight获取尺寸了,比如我们在onLayout方法中就会可以调用;

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); //1
}

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    、、、
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);//2
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth; //3
    mMeasuredHeight = measuredHeight; //4

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
复制代码

6、View的getDefaultSize方法

注释1处获得specMode;

从注释2处我们可知,如果我们自定义view直接继承自View,那么我们不管设置wrap_content还是match_parent效果都是一样的;

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec); //1
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY: //2
        result = specSize;
        break;
    }
    return result;
}
复制代码

三、布局流程layout

1、performLayout方法

布局流程的入口函数就是performLayout,如下所示,注释1处将decorView赋值给host,然后在注释2处执行host的layout,也就是DecorView的layout,DecorView、DecorView的父类FrameLayout、FrameLayout的父类ViewGroup都没有重写layout函数,因此实际调用的是View的layout,这块跟measure的逻辑是一样的;

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    mInLayout = true;
    final View host = mView; //1
    if (host == null) {
        return;
    }
    
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); //2
    mInLayout = false;
}
复制代码

2、View的layout方法

在View的layout方法中其实主要就是调用了onLayout,DecorView重写了onLayout,而在DecorView的onLayout中又调用了super.onLayout(changed, left, top, right, bottom);也就是FrameLayout的onLayout方法;

注释1处的setFrame方法主要是给当前view设置尺寸,具体实现是在注释2、3、4、5处分别给左上右下赋值;所以执行完layout过程后就可以通过getWidth和getHeight获得view的宽高了,我们一般在onDraw方法中调用这两个方法获取view的宽高;

public void layout(int l, int t, int r, int b) {
    、、、
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //1
    onLayout(changed, l, t, r, b);
    、、、
}

protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    // Invalidate our old position
    invalidate(sizeChanged);
    、、、
    mLeft = left; //2
    mTop = top; //3
    mRight = right; //4
    mBottom = bottom; //5
    、、、
    return changed;
}
复制代码

3、FrameLayout的onLayout方法

注释1处onLayout又调用了layoutChildren;

注释2处计算出所有子view的数量;注释3处循环遍历每一个子view,并执行子view的layout方法;因此又会执行到View的layout方法,进而又会执行到onLayout方法;如果子view又是一个viewgroup,那么还会循环执行上述操作;如果子view是一个具体的view,就是调用到view的onLayout,其实view的onLayout是一个空实现;尺寸的赋值在第2步已经介绍了;

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

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount(); //2

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft;
            int childTop;
            、、、        

            child.layout(childLeft, childTop, childLeft + width, childTop + height); //3
        }
    }
}
复制代码

四、绘制流程draw

1、performDraw

方法的调用链如下边注释1、2、3所示,最终会调用到View的draw方法;

private void performDraw() {
    、、、
    boolean canUseAsync = draw(fullRedrawNeeded); //1
    、、、
}

private boolean draw(boolean fullRedrawNeeded) {
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) { //2
        return false;
    }
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    、、、
    mView.draw(canvas); //3
    、、、
}
复制代码

2、View的draw方法

在view的draw方法中官方也给出了注释,注释中表示draw流程分为7步,在注释中也阐述的非常清楚了,我们不再重复,每一步都可以点进去对应的方法查看,我们主要看一下第三四步;

第3步是通过onDraw方法绘制自身,我们点进去发现是一个空实现,所以需要在具体的view中自己去实现;

第4步通过dispatchDraw(canvas)方法去绘制所有的子view,在View中是一个空实现,在viewgroup中给出了具体的实现;

public void draw(Canvas canvas) {

    /*
     * 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
    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;
    }
}
复制代码

3、ViewGroup中的dispatchDraw方法

注释1表示在dispatchDraw方法中调用drawChild来绘制子view,注释2表示又会调用每个child的draw方法,这样层层递归最终绘制出整个view;

protected void dispatchDraw(Canvas canvas) {
    final int childrenCount = mChildrenCount;        
    、、、
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime); //1
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
    }
    、、、
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime); //2
}
复制代码

五、注意点:

requestLayout和invalidate以及postInvalidate的区别

1、requestLayout

在view中调用requestLayout后,会递归调用parent的requestLayout,最终会调用到ViewRootImpl的requestLayout方法,如注释1所示,最终调用到performTraversals执行三大流程,因为requestLayout将ForceLayout标志位置为true,因此onMeasure和onLayout肯定会执行,onDraw会根据视图的左上右下参数以及是否是脏数据来决定是否重新执行;

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals(); //1
    }
}
复制代码

2、invalidate

invalidate不会设置请求布局的参数,因此onMesaure和onLayout不会执行,而onDraw会执行;

3、postInvalidate

子线程中调用此方法更新视图,实际就是通过handler切换到主线程中,最终还是执行的invalidate;

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享