Android开发学习之View测量绘制流程源码阅读记录

背景

这几天在读三个常见布局(RelativeLayout、FrameLayout和LinearLayout)的测量布局绘制流程时,涉及到了view的绘制过程draw(),所以干脆把view的测量和布局流程也看完拉倒。

view的这些流程开始于ViewRootImpl的私有方法performTraversals(),这个方法也很长,所以我只选读了跟三大流程直接有关的部分


ViewRootImpl#performTraversals

测量

跟measure()直接相关的代码如下

          if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) { // mHeight/mWidth是窗口的高度宽度,host.getMeasuredHeight/Width()是根view(decorView)的高度宽度
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); // 获取根view的尺寸大小
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    .. // 日志

                     // Ask host how big it wants to be // 开始测量根view,此时view树的测量工作开始
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    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) { // 根据权重再次测量
                        .. // 日志
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }

其中先是调用了getRootMeasureSpec()方法获取根view的measureSpec,代码如下

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) { // 根据传进来的根view尺寸,进行不同的makeMeasureSpec()处理

        case ViewGroup.LayoutParams.MATCH_PARENT: // 默认是这一种
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
}

以宽度为例,传进来的是lp.width,这个lp的初始化也在ViewRootImpl.performTraversals()里,代码如下

WindowManager.LayoutParams lp = mWindowAttributes;

mWindowAttributes属性则是在一开始就初始化了

final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();

WindowManager.LayoutParams的构造方法如下

public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            type = TYPE_APPLICATION;
            format = PixelFormat.OPAQUE;
}

所以lp的宽高都是match_parent。关于makeMeasureSpec()方法,参见安卓开发学习之MeasureSpec一文


从getRootMeasureSpec()方法返回后,主要调用了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);
        }
}

调用了View.measure()方法,代码如下

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        .. // optical模式,一般遇不到

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec; // view尺寸是否发生变化
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; // 是否是精确模式
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); // view宽高是否发生变化
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); // 是否需要重新布局

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded(); // 解析view展示方向,从左往右还是从右往左。此处略过

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) { // 当前view没有缓存的话,重新测量
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec); // view真正的测量过程
                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; // 并设置布局前要再进行测量
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
               .. // 没有调用setMeasureDimension()方法,程序会抛出异常。方法调用与否是根据标志位判断的
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
        // 更新缓存
}

可见,主要是调用了view.onMeasure()方法测量,这个方法是空方法,不同的子类去进行不同的实现,decorView调用的是父类FrameLayout的onMeasure(),其具体的实现参见安卓开发之FrameLayout测量流程源码阅读一文


布局

跟layout()相关的代码如下

        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); // 是否需要布局
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes; // 是否需要通知globalLayoutListener,一般是需要的
        if (didLayout) {
            performLayout(lp, mWidth, mHeight);

            .. // 后续处理
        }

triggerGlobalLayoutListener变量可以决定是否要通知globalLayoutListener,这个监听可以在布局完成后让监听者进行业务处理,主要是获取measuredWidth,不过进行完布局方面的业务处理后,一定要remove掉这个listener,否则会进入死循环


然后,调用performLayout,代码如下

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        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 {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            .. // 处理requestLayout,正常的绘制用不到
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
}

主要是调用了view.layout()方法,代码如下

public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { // 如果在View.measure()方法中是从缓存读来的尺寸信息
            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()
                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_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        .. // 处理自动填写autoFill
}

先是调用了setFrame()方法,代码如下

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

        .. // 日志

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { // 如果端点有变化
            changed = true;

            // Remember our drawn bit
            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);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); // renderNode只有在硬件加速渲染时才会有关联

            mPrivateFlags |= PFLAG_HAS_BOUNDS;


            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight); // 调用onSizeChanged()方法,这个也要子类去实现,然后更新轮廓outLine
            }

            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) { // mGhostView也跟硬件加速有关
                .. // 硬件加速有关
            }

            // Reset drawn bit to original value (invalidate turns it off)
            mPrivateFlags |= drawn;

            mBackgroundSizeChanged = true;
            mDefaultFocusHighlightSizeChanged = true;
            if (mForegroundInfo != null) {
                mForegroundInfo.mBoundsChanged = true;
            }

            notifySubtreeAccessibilityStateChangedIfNeeded(); // 处理回调
        }
        return changed;
}

可见setFrame()方法主要就是重新设置了四个端点

然后回到View.layout()方法,进入View.onLayout()方法

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

空方法,子类去实现。decorView调用的是父类FrameLayout的onLayout(),阅读记录参见安卓开发学习之FrameLayout的layout过程一文


绘制

回到ViewRootImpl.performTraversals(),其中跟draw()有关的代码如下

        if (!cancelDraw && !newSurface) { // 默认情况下,判断会成立
            .. // 处理transition动画

            performDraw();
        }

调用了performDraw()方法,代码如下

private void performDraw() {
        .. // 必要性判断

        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;

        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        .. // 处理硬件加速的情况
}

调用了draw()方法,代码如下

private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        .. // 合法性判断

        .. // firstDraw()处理,一般不用

        .. // 滚动到当前显示的窗口或焦点处,并处理滚动。此处略过

        final Rect dirty = mDirty;
        .. // mSurfaceHolder不为null,直接返回(但只要不用SurfaceView,这个就不用管)

        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); // dirty表示需要绘制的区域,这里绘制的区域要大于窗口区域
        }

        .. // 日志,下发onDraw()监听

        .. // 其他处理

        mAttachInfo.mDrawingTime =
                mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;

        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                .. // 硬件加速
            } else {
               
                .. // 硬件加速初始化

                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }

        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals(); // 此方法上来判断mTraversalScheduled是否为false,是false才执行后面的逻辑。但performTraversals()执行后,只要不执行
        }                         // 但performTraversals()执行后,正常情况下mTraversalScheduled一直为true 
}

主要还是调用了drawSoftware()方法,代码如下

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        final Canvas canvas;
        .. // 设置canvas为dirty

        try {
            .. // 日志,画布绘制颜色(默认没有颜色)

            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;

            .. // 日志
            try {
                .. // 画布的偏移、抖动等等

                mView.draw(canvas);

                ..
            } finally {
                ..
            }
        } finally {
            .. 
        }
        return true;
}

主要是调用了decorView的draw(canvas)方法,其实也就是View.draw(canvas)方法,关于这个方法的阅读,请参见文章安卓开发学习之View的draw(canvas)方法


结语

View绘制的三大流程:measure、layout、draw基本就这样了,这些源码都是来自api-26版本源代码,所以有很多额外的东西(主要是硬件加速),有兴趣的可以自己下去看看。

猜你喜欢

转载自blog.csdn.net/qq_37475168/article/details/80328802
今日推荐