深入了解Android的View工作原理(下)

660 阅读10分钟

本文章讲解的内容是深入了解Android的View工作原理,建议对着示例项目阅读文章,示例项目链接如下:

ViewDemo

本文章分析的相关的源码基于Android SDK 29(Android 10.0,即Android Q)

由于掘金文章字数限制,分为两篇发布:

深入了解Android的View工作原理(上)

深入了解Android的View工作原理(下)

绘制流程

绘制流程从**requestLayout()**方法开始,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        // 调用checkThread()方法
        checkThread();
        mLayoutRequested = true;
        // 调用scheduleTraversals()方法
        scheduleTraversals();
    }
}

checkThread()方法的作用是检查当前线程是不是创建ViewRootImpl所在的线程,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
void checkThread() {
    if (mThread != Thread.currentThread()) {
        // 如果当前线程不是创建ViewRootImpl所在的线程就抛出CalledFromWrongThreadException异常
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

scheduleTraversals()方法的作用是让Android系统优先执行跟View更新相关的异步消息优先处理跟View更新相关的逻辑,在深入了解Android消息机制和源码分析(Java层和Native层)(上)深入了解Android消息机制和源码分析(Java层和Native层)(下)这两篇文章有提及过,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    // 省略部分代码
    final Thread mThread;
    // 省略部分代码

    public ViewRootImpl(Context context, Display display) {
        // 得到当前线程
        mThread = Thread.currentThread();
    }

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 添加同步屏障消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 执行添加同步屏障消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    // 省略部分代码

    // 创建TraversalRunnable对象
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    // 省略部分代码
}

成员变量mTraversalRunnableTraversalRunnable类型,TraversalRunnableViewRootImpl的被关键字final内部类,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        // 调用doTraversal()方法
        doTraversal();
    }
}

看下**doTraversal()**方法,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 删除同步屏障消息
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        // 调用performTraversals()方法
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

看下**performTraversals()**方法,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
    // 省略部分代码

    if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
        // 省略部分代码

        if (!mStopped || mReportNextDraw) {
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                    (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                        + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                        + " mHeight=" + mHeight
                        + " measuredHeight=" + host.getMeasuredHeight()
                        + " coveredInsetsChanged=" + contentInsetsChanged);

                // 调用performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法,分别传入的是根视图的宽的MeasureSpec和高的MeasureSpec
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                // 实现WindowManager.LayoutParams中的weight,根据需要增加尺寸,并且在需要时重新测量
                int width = host.getMeasuredWidth();
                int height = host.getMeasuredHeight();
                boolean measureAgain = false;

                if (lp.horizontalWeight > 0.0f) {
                    width += (int) ((mWidth - width) * lp.horizontalWeight);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                if (lp.verticalWeight > 0.0f) {
                    height += (int) ((mHeight - height) * lp.verticalWeight);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }

                if (measureAgain) {
                    if (DEBUG_LAYOUT) Log.v(mTag,
                            "And hey let's measure once more: width=" + width
                                    + " height=" + height);
                    // 如果需要重新测量,就再次调用performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)方法,分别传入的是根视图的宽的MeasureSpec和高的MeasureSpec
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }

                layoutRequested = true;
            }
        }
    } else {
        // 省略部分代码
    }

    // 省略部分代码

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        // 调用performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)方法
        performLayout(lp, mWidth, mHeight);

        // 省略部分代码
    }

    // 省略部分代码

    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

    if (!cancelDraw) {
        // 省略部分代码

        // 调用performDraw()方法
        performDraw();
    } else {
        if (isViewVisible) {
            // 如果取消draw,同时View是可见的,就再次调用scheduleTraversals()方法
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }

    // 省略部分代码
}

View绘制三大流程分别从**performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)**方法、**performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)方法和performDraw()**方法开始。

measure流程

MeasureSpecView类的静态内部类,它代表了一个32位int值高2位代表SpecMode(测量模式)低30位代表SpecSize(在某个测量模式下的规格大小),源码如下所示:

// View.java
public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    /** @hide */
    @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
    @Retention(RetentionPolicy.SOURCE)
    public @interface MeasureSpecMode {}

    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;

    // 省略部分代码
}

SpecMode分为三种模式,如下所示:

  • UNSPECIFIED父元素没有对子元素添加任何约束,它可以是任何大小,这个模式一般是在系统内部使用。
  • EXACTLY父元素已经确定了子元素的精确大小,也就是子元素的最终大小由SpecSize的值决定,对应于LayoutParams中的match_parent和具体数值这两种模式。
  • AT_MOST子元素的大小不能大于父元素的SpecSize的值,子元素的默认大小,对应于LayoutParams中的wrap_content。

ViewMeasureSpec是由父元素的MeasureSpec自身的LayoutParams共同决定的。

measure流程ViewRootImpl类的**performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)**方法开始,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        // 调用View的measure(int widthMeasureSpec, int heightMeasureSpec)方法
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

看下View类的**measure(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:

// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // 省略部分代码

    if (forceLayout || needsLayout) {
        // 省略部分代码
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // 调用onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            // 省略部分代码
        }

        // 省略部分代码
    }

    // 省略部分代码
}

看下**onMeasure(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:

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

开发者可以重写这个方法来改变测量View的逻辑。

先看下**getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()**方法,源码如下所示:

// View.java
protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

}

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

getSuggestedMinimumWidth()方法返回的是View的建议的最小宽度,如果View没有背景,就返回minWidth,否则就返回背景的最小宽度

getSuggestedMinimumHeight()方法返回的是View的建议的最小高度,如果View没有背景,就返回minHeight,否则就返回背景的最小高度

然后看下**getDefaultSize(int size, int measureSpec)**方法,源码如下所示:

// View.java
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;
}

最后看下**setMeasuredDimension(int measuredWidth, int measuredHeight)**方法,源码如下所示:

// View.java
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;

        // 得到这个View的测量宽度
        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        // 得到这个View的测量高度
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    // 调用setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)方法
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

看下**setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)**方法,源码如下所示:

// View.java
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

ViewgetMeasuredWidth()方法使用到成员变量mMeasuredWidth的值,它的作用是返回原始的测量宽度ViewgetMeasuredHeight()方法使用到成员变量mMeasuredHeight的值,它的作用是返回原始的测量高度,源码如下所示:

// View.java
public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}

public final int getMeasuredHeight() {
    return mMeasuredHeight & MEASURED_SIZE_MASK;
}

前面看的是Viewmeasure过程,接下来看下ViewGroupmeasure过程,看下ViewGroup类的**measureChildren(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:

// View.java
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    // 循环执行
    for (int i = 0; i < size; ++i) {
        // 得到ViewGroup中的子元素
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            // 如果子元素的状态不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE),就测量子元素,调用measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)方法
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

看下**measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)**方法,源码如下所示:

// View.java
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    // 调用View的measure(int widthMeasureSpec, int heightMeasureSpec)方法
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

例子

最后看下一个例子:LinearLayout,它重写了**onMeasure(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:

// LinearLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        // 如果是垂直方向,就调用measureVertical(int widthMeasureSpec, int heightMeasureSpec)方法
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        // 如果是水平方向,就调用measureHorizontal(int widthMeasureSpec, int heightMeasureSpec)方法
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

这里只看垂直方向的这种情况,看下**measureVertical(int widthMeasureSpec, int heightMeasureSpec)**方法,源码如下所示:

// LinearLayout.java
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    // mTotalLength的值是所有子元素的高度加paddingTop和paddingBottom,要注意的是,它和LinearLayout本身的高度不同
    mTotalLength = 0;
    // 所有子元素的最大宽度
    int maxWidth = 0;
    int childState = 0;
    // 所有layout_weight属性的值小于等于0的子元素中宽度的最大值
    int alternativeMaxWidth = 0;
    // 所有layout_weight属性的值大于0的子元素中宽度的最大值
    int weightedMaxWidth = 0;
    boolean allFillParent = true;
    // 所有子元素的weight之和
    float totalWeight = 0;

    final int count = getVirtualChildCount();

    // 得到宽度的测量模式
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    // 得到高度的测量模式
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    boolean matchWidth = false;
    boolean skippedMeasure = false;

    final int baselineChildIndex = mBaselineAlignedChildIndex;
    final boolean useLargestChild = mUseLargestChild;

    int largestChildHeight = Integer.MIN_VALUE;
    int consumedExcessSpace = 0;

    int nonSkippedChildCount = 0;

    // 循环执行
    for (int i = 0; i < count; ++i) {
        // 得到子元素
        final View child = getVirtualChildAt(i);
        if (child == null) {
            mTotalLength += measureNullChild(i);
            continue;
        }

        if (child.getVisibility() == View.GONE) {
           i += getChildrenSkipCount(child, i);
           continue;
        }

        nonSkippedChildCount++;
        if (hasDividerBeforeChildAt(i)) {
            mTotalLength += mDividerHeight;
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        totalWeight += lp.weight;

        final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
        if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
            skippedMeasure = true;
        } else {
            // 省略部分代码
            final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
            // 调用measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)方法
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);

            // 省略部分代码
        }

        // 省略部分代码
    }

    // 省略部分代码

    // mTotalLength再加上paddingTop和paddingBottom之和
    mTotalLength += mPaddingTop + mPaddingBottom;

    int heightSize = mTotalLength;

    // 取高度大小和建议最小高度的最大值
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

    // 省略部分代码

    if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
        maxWidth = alternativeMaxWidth;
    }

    // maxWidth再加上paddingLeft和paddingRight之和
    maxWidth += mPaddingLeft + mPaddingRight;

    // 取最大宽度和建议最小宽度的最大值
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // 调用setMeasuredDimension(int measuredWidth, int measuredHeight)方法
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            heightSizeAndState);

    if (matchWidth) {
        forceUniformWidth(count, heightMeasureSpec);
    }
}

measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight)方法的作用是测量LinearLayout中子元素,让这些子元素执行measure流程,同时系统通过成员变量mTotalLength来存储LinearLayout垂直方向初步高度,每测量一个子元素,都会使成员变量mTotalLength加上子元素的高度子元素在垂直方向上的margin属性和padding属性

layout流程

layout流程ViewRootImpl类的**performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight)**方法开始,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                           int desiredWindowHeight) {
    // 省略部分代码

    final View host = mView;
    if (host == null) {
        return;
    }
    if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
        Log.v(mTag, "Laying out " + host + " to (" +
                host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
    }

    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        // 调用View的layout(int l, int t, int r, int b)方法
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        // 省略部分代码
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

调用layout(int l, int t, int r, int b)方法,并且left的位置传入0top的位置传入0right的位置传入测量宽度bottom的位置传入测量高度,源码如下所示:

// View.java
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
    // 省略部分代码

    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(boolean changed, int left, int top, int right, int bottom)方法
        onLayout(changed, l, t, r, b);

        // 省略部分代码
    }

    // 省略部分代码
}

看下isLayoutModeOptical(Object o)方法,它的作用是如果是传进来是一个使用光学边界布局的ViewGroup就返回true否则就返回false,源码如下所示:

// View.java
public static boolean isLayoutModeOptical(Object o) {
    return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}

先看下**setOpticalFrame(int left, int top, int right, int bottom)**方法,源码如下所示:

// View.java
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
    Insets parentInsets = mParent instanceof View ?
            ((View) mParent).getOpticalInsets() : Insets.NONE;
    Insets childInsets = getOpticalInsets();
    // 调用setFrame(int left, int top, int right, int bottom)方法
    return setFrame(
            left   + parentInsets.left - childInsets.left,
            top    + parentInsets.top  - childInsets.top,
            right  + parentInsets.left + childInsets.right,
            bottom + parentInsets.top  + childInsets.bottom);
}

然后看下setFrame(int left, int top, int right, int bottom)方法,这个方法的作用是确定View的四个顶点位置也就是确定了子元素在父元素中的位置,源码如下所示:

// View.java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (DBG) {
        Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                + right + "," + bottom + ")");
    }

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        int drawn = mPrivateFlags & PFLAG_DRAWN;

        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // 使旧的位置无效
        invalidate(sizeChanged);

        // 将left赋值给成员变量mLeft
        mLeft = left;
        // 将top赋值给成员变量mTop
        mTop = top;
        // 将right赋值给成员变量mRight
        mRight = right;
        // 将bottom赋值给成员变量mBottom
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        // 省略部分代码
    }
    return changed;
}

ViewgetWidth()的值就是使用mRight的值减mLeft的值ViewgetHeight()的值就是使用mBottom的值减mTop的值,源码如下所示:

// View.java
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
    return mRight - mLeft;
}

@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {
    return mBottom - mTop;
}

接着上面,看下**onLayout(boolean changed, int left, int top, int right, int bottom)**方法,源码如下所示:

// View.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

开发者可以重写这个方法来改变布局View的逻辑。

例子

最后看下一个例子:LinearLayout,它重写了**onLayout(boolean changed, int l, int t, int r, int b)**方法,源码如下所示:

// LinearLayout.java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        // 如果是垂直方向,就调用layoutVertical(int left, int top, int right, int bottom)方法
        layoutVertical(l, t, r, b);
    } else {
        // 如果是垂直方向,就调用layoutHorizontal(int left, int top, int right, int bottom)方法
        layoutHorizontal(l, t, r, b);
    }
}

这里只看垂直方向的这种情况,看下**layoutVertical(int left, int top, int right, int bottom)**方法,源码如下所示:

// LinearLayout.java
void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    // 父元素的默认宽度
    final int width = right - left;
    // 子元素默认的right的值
    int childRight = width - mPaddingRight;

    // 子元素的可用空间
    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;

    // 根据设置的gravity属性,设置第一个子元素的top的值
    switch (majorGravity) {
       case Gravity.BOTTOM:
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;

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

            // 得到子元素的LayoutParams
            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);
            // 根据子元素的gravity属性设置left的值
            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;
            }

            // 子元素的top的值加上marginTop的值
            childTop += lp.topMargin;
            // 调用setChildFrame(View child, int left, int top, int width, int height)方法设置子元素在父元素的布局位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}

setChildFrame(View child, int left, int top, int width, int height)方法的作用是设置子元素在父元素的布局位置,源码如下所示:

// LinearLayout.java
private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}

这个方法调用了子元素layout(int l, int t, int r, int b)方法,使子元素执行layout流程

draw流程

draw流程ViewRootImpl类的**performDraw()**方法开始,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
    // 省略部分代码

    try {
        // 调用draw(Canvas canvas)方法
        boolean canUseAsync = draw(fullRedrawNeeded);
        if (usingAsyncReport && !canUseAsync) {
            mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
            usingAsyncReport = false;
        }
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

    // 省略部分代码
}

看下**draw(Canvas canvas)**方法,源码如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
@CallSuper
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    int saveCount;

    // 第一步:如果有必要,就画背景
    drawBackground(canvas);

    // 一般情况下,如果可能的话会跳过第二步和第五步
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // 第三步:画View的内容
        onDraw(canvas);

        // 第四步:画子元素
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        // 覆盖(overlay)是内容的一部分,它在前景下面绘制
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // 第六步:绘制装饰(前景、滚动条)
        onDrawForeground(canvas);

        // 第七步:绘制默认焦点突出显示
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // 执行到这里代表整个绘制流程就执行完成了
        return;
    }

    boolean drawTop = false;
    boolean drawBottom = false;
    boolean drawLeft = false;
    boolean drawRight = false;

    float topFadeStrength = 0.0f;
    float bottomFadeStrength = 0.0f;
    float leftFadeStrength = 0.0f;
    float rightFadeStrength = 0.0f;

    // 第二步:如果有必要,就保存画布的图层,准备褪色(fading)
    int paddingLeft = mPaddingLeft;

    final boolean offsetRequired = isPaddingOffsetRequired();
    if (offsetRequired) {
        paddingLeft += getLeftPaddingOffset();
    }

    int left = mScrollX + paddingLeft;
    int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    int top = mScrollY + getFadeTop(offsetRequired);
    int bottom = top + getFadeHeight(offsetRequired);

    if (offsetRequired) {
        right += getRightPaddingOffset();
        bottom += getBottomPaddingOffset();
    }

    final ScrollabilityCache scrollabilityCache = mScrollCache;
    final float fadeHeight = scrollabilityCache.fadingEdgeLength;
    int length = (int) fadeHeight;

    // 剪辑(clip)褪色长度,如果顶部和底部褪色重叠将会产生奇怪的工件
    if (verticalEdges && (top + length > bottom - length)) {
        length = (bottom - top) / 2;
    }

    // 如果有必要,还可以剪辑(clip)水平渐变
    if (horizontalEdges && (left + length > right - length)) {
        length = (right - left) / 2;
    }

    if (verticalEdges) {
        topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
        drawTop = topFadeStrength * fadeHeight > 1.0f;
        bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
        drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
    }

    if (horizontalEdges) {
        leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
        drawLeft = leftFadeStrength * fadeHeight > 1.0f;
        rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
        drawRight = rightFadeStrength * fadeHeight > 1.0f;
    }

    saveCount = canvas.getSaveCount();
    int topSaveCount = -1;
    int bottomSaveCount = -1;
    int leftSaveCount = -1;
    int rightSaveCount = -1;

    int solidColor = getSolidColor();
    if (solidColor == 0) {
        if (drawTop) {
            topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
        }

        if (drawBottom) {
            bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
        }

        if (drawLeft) {
            leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
        }

        if (drawRight) {
            rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
        }
    } else {
        scrollabilityCache.setFadeColor(solidColor);
    }

    // 第三步:画View的内容
    onDraw(canvas);

    // 第四步:画子元素
    dispatchDraw(canvas);

    // 第五步:如果有必要,就画褪色的边缘(fading edges)和恢复图层(restore layers)
    final Paint p = scrollabilityCache.paint;
    final Matrix matrix = scrollabilityCache.matrix;
    final Shader fade = scrollabilityCache.shader;

    // 必须按照保存的顺序进行恢复
    if (drawRight) {
        matrix.setScale(1, fadeHeight * rightFadeStrength);
        matrix.postRotate(90);
        matrix.postTranslate(right, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(rightSaveCount, p);

        } else {
            canvas.drawRect(right - length, top, right, bottom, p);
        }
    }

    if (drawLeft) {
        matrix.setScale(1, fadeHeight * leftFadeStrength);
        matrix.postRotate(-90);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(leftSaveCount, p);
        } else {
            canvas.drawRect(left, top, left + length, bottom, p);
        }
    }

    if (drawBottom) {
        matrix.setScale(1, fadeHeight * bottomFadeStrength);
        matrix.postRotate(180);
        matrix.postTranslate(left, bottom);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(bottomSaveCount, p);
        } else {
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }
    }

    if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(topSaveCount, p);
        } else {
            canvas.drawRect(left, top, right, top + length, p);
        }
    }

    canvas.restoreToCount(saveCount);

    drawAutofilledHighlight(canvas);

    // 覆盖(overlay)是内容的一部分,它在前景下面绘制
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }

    // 第六步:绘制装饰(前景和滚动条)
    onDrawForeground(canvas);

    if (debugDraw()) {
        debugDrawFocus(canvas);
    }
}

看下View类的**dispatchDraw(Canvas canvas)**方法,源码如下所示:

// View.java
protected void dispatchDraw(Canvas canvas) {

}

这个方法没有任何逻辑处理ViewGroup类继承View类,看下ViewGroup重写了这个方法,源码如下所示:

// ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    // 得到子元素的数量
    final int childrenCount = mChildrenCount;
    // 得到子元素的数组,也就是View数组
    final View[] children = mChildren;
    int flags = mGroupFlags;

    if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
        final boolean buildCache = !isHardwareAccelerated();
        // 循环执行
        for (int i = 0; i < childrenCount; i++) {
            // 得到子元素
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                // 如果这个View是可见状态,就处理其动画
                final LayoutParams params = child.getLayoutParams();
                attachLayoutAnimationParameters(child, params, i, childrenCount);
                bindLayoutAnimation(child);
            }
        }

        final LayoutAnimationController controller = mLayoutAnimationController;
        if (controller.willOverlap()) {
            mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
        }

        controller.start();

        mGroupFlags &= ~FLAG_RUN_ANIMATION;
        mGroupFlags &= ~FLAG_ANIMATION_DONE;

        if (mAnimationListener != null) {
            mAnimationListener.onAnimationStart(controller.getAnimation());
        }
    }

    // 省略部分代码
    final ArrayList<View> preorderedList = usingRenderNodeProperties
            ? null : buildOrderedChildList();
    final boolean customOrder = preorderedList == null
            && isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {
        // 省略部分代码
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            // 调用drawChild(Canvas canvas, View child, long drawingTime)方法
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    // 省略部分代码
}

看下**drawChild(Canvas canvas, View child, long drawingTime)**方法,源码如下所示:

// ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

这个方法调用View的**draw(Canvas canvas)**方法。

总结一下,draw流程分为以下七步

  1. 画背景。
  2. 如果有必要,就保存画布的图层,准备褪色(fading)。
  3. 画View的内容。
  4. 画子元素。
  5. 如果有必要,画褪色的边缘(fading edges)和恢复图层(restore layers)。
  6. 绘制装饰(前景和滚动条)。
  7. 绘制默认焦点突出显示。

例子

最后看下一个例子:LinearLayout,它重写了**onDraw(Canvas canvas)**方法,源码如下所示:

// LinearLayout.java
@Override
protected void onDraw(Canvas canvas) {
    if (mDivider == null) {
        // 如果没有分割线,就结束方法
        return;
    }

    if (mOrientation == VERTICAL) {
        // 如果是垂直方向,就调用drawDividersVertical(Canvas canvas)方法
        drawDividersVertical(canvas);
    } else {
        // 如果是水平方向,就调用drawDividersHorizontal(Canvas canvas)方法
        drawDividersHorizontal(canvas);
    }
}

这里只看垂直方向的这种情况,看下**drawDividersVertical(Canvas canvas)**方法,源码如下所示:

// LinearLayout.java
void drawDividersVertical(Canvas canvas) {
    // 得到子元素的虚拟数量
    final int count = getVirtualChildCount();
    // 循环执行
    for (int i = 0; i < count; i++) {
        // 得到子元素
        final View child = getVirtualChildAt(i);
        if (child != null && child.getVisibility() != GONE) {
            if (hasDividerBeforeChildAt(i)) {
                // 如果子元素的状态不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE),同时这个View有分割线,就画水平的分割线
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final int top = child.getTop() - lp.topMargin - mDividerHeight;
                drawHorizontalDivider(canvas, top);
            }
        }
    }

    if (hasDividerBeforeChildAt(count)) {
        // 得到最后一个不是隐藏状态,也就是可见状态(VISIBLE)或者不可见状态(INVISIBLE)的View
        final View child = getLastNonGoneChild();
        int bottom = 0;
        if (child == null) {
            bottom = getHeight() - getPaddingBottom() - mDividerHeight;
        } else {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            bottom = child.getBottom() + lp.bottomMargin;
        }
        // 画水平的分割线
        drawHorizontalDivider(canvas, bottom);
    }
}

这个方法的作用是画水平的分割线

在子线程更新UI的问题

先看下如下第一个例子,代码如下所示:

package com.tanjiajun.viewdemo

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

/**
 * Created by TanJiaJun on 2020/10/8.
 */
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 创建子线程,并且启动它
        Thread {
            // 在子线程更新UI,设置id为tv_content的TextView的文本为谭嘉俊
            findViewById<TextView>(R.id.tv_content).text = "谭嘉俊"
        }.start()
    }

}

这段代码是在子线程更新UI,结果很顺利地执行完毕,并且符合预期。

我修改下第一个例子,得到第二个例子,代码如下所示:

package com.tanjiajun.viewdemo

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

/**
 * Created by TanJiaJun on 2020/10/8.
 */
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 创建子线程,并且启动它
        Thread {
            // 让子线程睡眠一秒
            Thread.sleep(1000)
            // 在子线程更新UI,设置id为tv_content的TextView的文本为谭嘉俊
            findViewById<TextView>(R.id.tv_content).text = "谭嘉俊"
        }.start()
    }

}

这段代码也是在子线程更新UI并且让子线程睡眠一秒,结果就抛出了如下异常:

2020-10-08 17:02:25.544 8619-8665/com.tanjiajun.viewdemo E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.tanjiajun.viewdemo, PID: 8619
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
        at android.view.View.requestLayout(View.java:23093)
        at android.widget.TextView.checkForRelayout(TextView.java:8908)
        at android.widget.TextView.setText(TextView.java:5730)
        at android.widget.TextView.setText(TextView.java:5571)
        at android.widget.TextView.setText(TextView.java:5528)
        at com.tanjiajun.viewdemo.MainActivity$onCreate$1.run(MainActivity.kt:20)
        at java.lang.Thread.run(Thread.java:764)

抛出了CalledFromWrongThreadException异常,在前面讲解checkThread()方法的时候也提及过,这个方法的作用是检查当前线程是不是创建ViewRootImpl所在的线程如果是就通知View执行绘制流程否则就抛出CalledFromWrongThreadException异常,示例代码的ViewRootImpl是在主线程创建的,也就是判断是否为主线程第一个例子没有抛出CalledFromWrongThreadException异常的原因是,因为调用setText(CharSequence text)方法的时候ViewRootImpl还没创建,View的绘制流程会在Activity的onResume方法之后执行,也就是ViewRootImpl是在onResume方法之后创建的,所以checkThread()方法还没调用,因此这个时候通知UI刷新就不会抛出CalledFromWrongThreadException异常,第二个例子抛出CalledFromWrongThreadException异常的原因是,因为让线程睡眠一秒,可能这个时候onResume方法已经执行了,并且已经在主线程 创建ViewRootImpl,这个时候调用setText(CharSequence text)通知UI刷新就会调用checkThread()方法,然后得到当前线程是子线程,和主线程不是同一个线程,因此就抛出CalledFromWrongThreadException异常。

其实Google是如下说法:

The Android UI toolkit is not thread-safe.

Google的意思是Android的UI toolkit不是线程安全的,Google也没说不允许在工作线程(非主线程)中更新UI。

题外话

介绍一个Android开源项目(AOSP)的代码搜索工具,网址如下:

Android开源项目(AOSP)的代码搜索工具

我的GitHub:TanJiaJunBeyond

Android通用框架:Android通用框架

我的掘金:谭嘉俊

我的简书:谭嘉俊

我的CSDN:谭嘉俊