Android-View绘制流程浅析

这段时间学习了下View的绘制流程,本着好记性不如烂笔头的原则,尝试将这些内容记录下来,用于巩固和总结。
这次学习的源码是基于Android SDK25来学习的,相比于之前版本的源码有了些许改变。
对于部分代码的功能和作用没有专门在正文中写出,而是以注释的形式写在了代码中。

基本流程

流程可大致分为两个部分,首先是在Activity的onCreate阶段设置contentView,另一个阶段则是在Activity.onResume阶段后的绘制过程。
大致流程图如下:

setContent流程图.png

绘制流程.png

流程解析

setContentView

刚开始学习Android时,默认生成的hello world中的OnCreate的方法里调用的就是setContentView(R.layout.activity_main);
而目前默认的MainActivity继承于AppCompatActivity

在API22之后Google遗弃了ActionBarActivity,推荐我们也可以说是强制我们使用AppCompatActivity。

在AppCompatActivity.java中可以看到如下的代码:

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

在这3个重载方法setContentView中都会调用getDelegate()的setContentView()方法。而getDelegate()则会根据不同的api版本返回不同的Delegate实例,这些实例之间是相互继承的关系

AppCompatDelegate.png

最后我们在AppCompatDelegateImplV9中找到了setContentView的实现方法

@Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mOriginalWindowCallback.onContentChanged();
    }

上面的代码中ensureSubDecor()方法用于生成DecorView和subDecor。与之前版本中Activity生成的DecorView不同的地方在于添加了一层布局FitWindowsLinearLayout,并将R.id.content放入了其中。这里涉及到的流程比较复杂,如果想详细了解这部分代码推荐看这篇文章
到这里终于看见了比较熟悉的布局加载的代码,在这里将我们的在onCreate中的设置的contentView放了进去。

updateViewLayout

在Activity的OnResume()方法运行后,ActivityThread.java会调用handleResumeActivity()方法,并在其中调用View的updateViewLayout(),而ViewManager是一个接口,ViewGroup实现了此接口:

@Override
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (!checkLayoutParams(params)) {
            throw new IllegalArgumentException("Invalid LayoutParams supplied to " + this);
        }
        if (view.mParent != this) {
            throw new IllegalArgumentException("Given view not a child of " + this);
        }
        view.setLayoutParams(params);
    }

在其中进行了一些检查后调用了view.setLayoutParams的方法。

public void setLayoutParams(ViewGroup.LayoutParams params) {
        //判断params是否为空
        if (params == null) {
            throw new NullPointerException("Layout parameters cannot be null");
        }
        mLayoutParams = params;
        //根据的布局方向解析布局参数
        resolveLayoutParams();
        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).onSetLayoutParams(this, params);
        }
        requestLayout();
    }

在此方法中进行了参数的赋值和方法的回调,而requestLayout()则循环调用了 mParent.requestLayout()方法,最终调用到的是ViewRootImpl.java的requestLayout()方法:

 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //判断调用线程是否为主线程
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

其中scheduleTraversals()方法则是用于通知开始绘制的重要方法:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //设置一个同步分割符,用于分割该消息之后的消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //通过mChoreographer异步调用mTraversalRunnable
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

其中mChoreographer是一个消息处理器,用于控制同步处理输入(Input)、动画(Animation)、绘制(Draw)三个UI操作,具体分析推荐这篇文章
其中的mTraversalRunnable的在它的run方法中调用了doTraversal()方法:

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

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

            performTraversals();

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

至此我们终于看到了performTraversals()方法。

performTraversals()

performTraversals()中做了非常多的处理,这里我们只关注于绘制相关的三大流程,简化代码如下:

private void performTraversals() {
......
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

    //Measure流程入口
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            
......
    //perform流程入口
    performLayout(lp, mWidth, mHeight);
......
    //perform流程入口
    performDraw();
}

MeasureSpec

MeasureSpec是一个十分特殊的类,它用于确定view的测量规格,代表了一个32位的int值,由SpecMode(高2位)+SpecSize(低30位)两部分组成。
SpecMode有以下三种:

  1. UNSPECIFIED:父容器不作限制,一般用于系统内部。
  2. EXACTLY:精确模式,大小为SpecSize,对应LayoutParams中的match_parent和具体数值。
  3. AT_MOST:最大模式,大小不能大于SpecSize,对应于LayoutParams中的warp_content。

它的创建方法为:

public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            //当API<=17时,使用旧的创建方式
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

而此方法主要由两种方式调用:getRootMeasureSpec()和getChildMeasureSpec()

getRootMeasureSpec()

用于DecorView确定MeasureSpec,主要由窗口尺寸和自身的LayoutParams共同决定。具体规则如下:

LayoutParams宽/高参数 生成的MeasureSpec
LayoutParams.match_parent和具体数值 EXACTLY
LayoutParams.warp_content AT_MOST
getChildMeasureSpec()

用于普通view确定MeasureSpec,主要由父容器的MeasureSpec和自身的LayoutParams共同决定。具体规则如下:

ChildLayoutParams\ParentSpecMode EXACTLY AT_MOST UNSPEECIFIED
dp/px EXACTLY childSize EXACTLY childSize EXACTLY childSzie
match_parent EXACTLY parentsize AT_MOST parentSize UNSPECIFIED 0
warp_content AT_MOST parentsize AT_MOST parentSize UNSPECIFIED 0

measure

测量流程的入口为performMeasure():

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

可以在代码中看到,该方法调用了mView.measure(childWidthMeasureSpec, childHeightMeasureSpec),将传入的MeasureSpec传入的到mView的measure方法中。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        //判断是否是Optical bound
        boolean optical = isLayoutModeOptical(this);
        //如果和父布局在Optical bound属性上不同则需要对MeasureSpec进行相应的处理
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // 通过传入的MeasureSpec生成相应的key
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        //mMeasureCache用于存储在特定的MeasureSpec(即上一行中的key)下的测量结果
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        //判断是否需要强制进行Layout
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // 判断是否需要测量布局,优化测量流程。
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // 清除PFLAG_MEASURED_DIMENSION_SET标志
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            //如果为forceLayout或者API<19时(mMeasureCache中无相应的key),走正常测量流程onMeasure()
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                //标志layout前不必再测量
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                //将缓存值读出并通过setMeasuredDimensionRaw存入相应的成员变量
                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;
            }

            // 通过这个标志位判断重写onMeasure的时候是否调用了setMeasuredDimension()
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        //存储本次测量的MeasureSpec,用于下次测量比较
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        //通过上面的key存储对应的测量结果
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

在上述代码中可以看到,当需要测量且不从缓存中取出相应结果时,需要调用onMeasure方法:

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

onMeasure方法中默认实现只是调用了setMeasuredDimension()方法,传入的参数是通过getDefaultSize方法获取相应的specSize,规则如下:若SpecMode为UNSPECIFIED则取SuggestedMinimum,否则取传入的specSize。其中SuggesttedMinmun为设置的minWidth/minHeight与background的大小的最大值。

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;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

可见,在setMeasuredDimension中仅对Optical bound进行了相应的处理后便调用了setMeasuredDimensionRaw方法。

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

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

在此处便是将相应的测量结果存入成员变量,并设置相应的标志位。
上述过程是单个view的绘制流程,在viewGroup中会有一些对于子view的处理方法:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

measureChildren的方法依次遍历所有子view并对Visibility不为Gone的view调用measureChild()方法

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
        //根据父view的MeasureSpec和自身的LayoutParams生成相应的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

上述代码可见,生成了相应的Measure之后传入到了view的Measure方法中,而生成规则便是上文中普通view的MeasureSpec生成规则。
除此之外还有一个measureChildWithMargins()方法,与上面的measureChild方法类似,区别在于getChildMeasureSpec时的第二个参数加入Margin所占空间和父布局已经使用的空间。

layout

布局流程的入口为performLayout()方法:

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

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

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            //调用mView的layout方法,传入四个参数分别代表该view离父布局的左、上、右、下的距离
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            //请求布局的个数
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                //获取仍然需要布局的view集合
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    //设置标志位,标志正在进行处理第二次布局请求
                    mHandlingLayoutInLayoutRequest = true;

                    // 需要进行布局的view的个数
                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        Log.w("View", "requestLayout() improperly called by " + view +
                                " during layout: running second layout pass");
                        //依次调用requestLayout()
                        view.requestLayout();
                    }
                    //对view树进行重新测量
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    //进行第二次布局
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    //清除标志位,标志第二次布局请求结束
                    mHandlingLayoutInLayoutRequest = false;

                    // 获取还需要进行布局的view集合
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // 加入到下一个frame处理,防止无限循环
                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View", "requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame");
                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }

            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;  
    }

在performLayout()的过程中调用了DecorView的layout()方法,但是DecorView中未复写此方法,在viewGroup中直接调用了view.layout()方法:

@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
        //通过标志位判断是否需要测量,该标志位会在measure()方法中被清除
        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;
        //判断该view的父布局是否为Optical bound并进行相应的处理
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //调用onlayout进行布局
            onLayout(changed, l, t, r, b);

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

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            //进行layoutChange监听回调
            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.java中此方法的实现为空,而ViewGroup中此方法为抽象方法,查看一下FrameLayout的实现方法:

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

在FrameLayout的onLayout方法调用了layoutChildren()方法:

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        //获取子view数量
        final int count = getChildCount();

        //获取FrameLayout的边界
        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        //遍历所有的子view
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //对Visibility不为Gone的view进行操作
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //在measure阶段会对所有的子view进行测量,这里获取测量后的大小
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                //获取子view的gravity属性,未设置则设置为默认Gravity.TOP | Gravity.START
                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                //获取Layout的方向RTL或则LTR
                final int layoutDirection = getLayoutDirection();
                //根据gravity(Start/End)和layoutDirection(RTL/LTR)确定水平方向排布方式的绝对值,例如RLT的START为Right,与LTR布局相反
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                //确定竖直方向排布方式
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                //根据不同的方向对子view的位置做不同的排布
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }

                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                //将处理后的位置信息传入子view的layout方法进行布局
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

Draw

结束了Layout流程后,就到了三部曲的最后一步绘制流程。入口为performDraw():

private void performDraw() {
        if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
            return;
        }

        //将mFullRedrawNeeded存入局部变量并置为false,该值用于表示是否需要完全重新绘制
        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;

        //mIsDrawing用于标识正在绘制
        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            //调用draw方法并传入fullRedrawNeeded确定是否需要完全重新绘制
            draw(fullRedrawNeeded);
        } finally {
            //正在绘制标志结束
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        // 省略部分关于渲染器的代码
        ......
    }

在performDraw中调用了draw方法:

private void draw(boolean fullRedrawNeeded) {
        //判断当前是存在有效的surface
        Surface surface = mSurface;
        if (!surface.isValid()) {
            return;
        }

        if (DEBUG_FPS) {
            trackFPS();
        }
        //加入绘制监听
        if (!sFirstDrawComplete) {
            synchronized (sFirstDrawHandlers) {
                sFirstDrawComplete = true;
                final int count = sFirstDrawHandlers.size();
                for (int i = 0; i< count; i++) {
                    mHandler.post(sFirstDrawHandlers.get(i));
                }
            }
        }
         
        //将界面滑动至所需位置
        scrollToRectOrFocus(null, false);
        //界面如果进行滑动,分发滑动监听事件
        if (mAttachInfo.mViewScrollChanged) {
            mAttachInfo.mViewScrollChanged = false;
            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
        }
        //根据滑动是否完成做出相应的处理
        boolean animating = mScroller != null && mScroller.computeScrollOffset();
        final int curScrollY;
        if (animating) {
            curScrollY = mScroller.getCurrY();
        } else {
            curScrollY = mScrollY;
        }
        //RootView滑动监听回调
        if (mCurScrollY != curScrollY) {
            mCurScrollY = curScrollY;
            fullRedrawNeeded = true;
            if (mView instanceof RootViewSurfaceTaker) {
                ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
            }
        }

        final float appScale = mAttachInfo.mApplicationScale;
        final boolean scalingRequired = mAttachInfo.mScalingRequired;

        int resizeAlpha = 0;
        //获取需要绘制的区域
        final Rect dirty = mDirty;
        if (mSurfaceHolder != null) {
            // The app owns the surface, we won't draw.
            dirty.setEmpty();
            if (animating && mScroller != null) {
                mScroller.abortAnimation();
            }
            return;
        }

        //当需要完全重新绘制时,将dirty的大小设置为整个屏幕大小
        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }

        if (DEBUG_ORIENTATION || DEBUG_DRAW) {
            Log.v(mTag, "Draw " + mView + "/"
                    + mWindowAttributes.getTitle()
                    + ": dirty={" + dirty.left + "," + dirty.top
                    + "," + dirty.right + "," + dirty.bottom + "} surface="
                    + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
                    appScale + ", width=" + mWidth + ", height=" + mHeight);
        }

        //通知已注册的监听器,绘制过程即将开始
        mAttachInfo.mTreeObserver.dispatchOnDraw();

        int xOffset = -mCanvasOffsetX;
        int yOffset = -mCanvasOffsetY + curScrollY;
        final WindowManager.LayoutParams params = mWindowAttributes;
        final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
        if (surfaceInsets != null) {
            xOffset -= surfaceInsets.left;
            yOffset -= surfaceInsets.top;

            // Offset dirty rect for surface insets.
            dirty.offset(surfaceInsets.left, surfaceInsets.right);
        }
        //无障碍模式相关处理
        boolean accessibilityFocusDirty = false;
        final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
        if (drawable != null) {
            final Rect bounds = mAttachInfo.mTmpInvalRect;
            final boolean hasFocus = getAccessibilityFocusedRect(bounds);
            if (!hasFocus) {
                bounds.setEmpty();
            }
            if (!bounds.equals(drawable.getBounds())) {
                accessibilityFocusDirty = true;
            }
        }

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

        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            //省略部分硬件加速相关处理的代码
            ......

                //调用drawSoftware进行绘制
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }

        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
    }

ViewRootImpl的draw()方法主要对view的滚动和硬件加速进行处理,而主要的绘制流程是drawSoftware()方法

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

        // Draw with software renderer.
        final Canvas canvas;
        try {
            final int left = dirty.left;
            final int top = dirty.top;
            final int right = dirty.right;
            final int bottom = dirty.bottom;
            //根据draw()中生成的dirty创建一个被锁定绘制区域的canvas
            canvas = mSurface.lockCanvas(dirty);

            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }

            //设置画布密度
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            Log.e(mTag, "Could not lock surface", e);
            // Don't assume this is due to out of memory, it could be
            // something else, and if it is something else then we could
            // kill stuff (or ourself) for no reason.
            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }

        try {
            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                        + canvas.getWidth() + ", h=" + canvas.getHeight());
                //canvas.drawARGB(255, 255, 0, 0);
            }

            // 如果canvas图层有alpha则清除颜色
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
            // 重置dirty
            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;

            if (DEBUG_DRAW) {
                Context cxt = mView.getContext();
                Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                        ", metrics=" + cxt.getResources().getDisplayMetrics() +
                        ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
            }
            try {
                //设置canvas的偏离值
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
                //调用DecorView的draw方法
                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {
                if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;
                }
            }
        } finally {
            try {
                //surface更新视图并释放canvas
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
        return true;
    }

由代码可以看出drawSoftware()主要是生成了一个画布,并通过调用mView.draw()进行view绘制,并更新到surface上。
DecorView的draw方法比较简单,在super.draw()后增加了mMenuBackground的draw。因此主要看View的Draw方法:

@CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        //通过标志位判断当前view是否是透明的
        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)
         */
        int saveCount;
        //当view不是透明的时候绘制背景
        if (!dirtyOpaque) {
            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) {
            // 不透明时绘制view的内容,即调用onDraw
            if (!dirtyOpaque) onDraw(canvas);

            // 绘制子view,通过传递canvas,将绘制事件传递给子view
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // 绘制装饰物,前景滑动块等
            onDrawForeground(canvas);

            // we're done...
            return;
        }

        //省略不常见情况的部分代码,流程与上述代码类似
        ......
    }

按照上述的绘制步骤,第一步为绘制背景,即drawBackground():

private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        //根据布局过程中获取的位置信息确定background的边界
        setBackgroundBounds();

        // Attempt to use a display list if requested.
        if (canvas.isHardwareAccelerated() && mAttachInfo != null
                && mAttachInfo.mHardwareRenderer != null) {
            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

            final RenderNode renderNode = mBackgroundRenderNode;
            if (renderNode != null && renderNode.isValid()) {
                setBackgroundRenderNodeProperties(renderNode);
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                return;
            }
        }
        //获取偏移量,根据偏移量在不同位置绘制背景
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }

第三步为绘制view的内容,即onDraw()方法,此方法为空实现,因为内容应由使用者决定其具体实现。
第四步绘制子view,即dispatchDraw()方法,在View.java中此方法为空实现,dispatchDraw()一般是针对存在子view的布局即ViewGroup,因此查看ViewGroup.dispatchDraw():

@Override
    protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;

        //省略部分动画相关的代码
        .....
        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);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }

            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                //对可见的View调用了drawChild方法
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        //省略部分代码
    }

在上述代码中调用了drawChild()方法,而此方法是调用View.draw(Canvas canvas, ViewGroup parent, long drawingTime)方法

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        //省略部分代码
        ......
        //对画布进行裁剪
        if (!drawingWithRenderNode) {
            // apply clips directly, since RenderNode won't do it for this draw
            if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
                if (offsetForScroll) {
                    canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
                } else {
                    if (!scalingRequired || cache == null) {
                        canvas.clipRect(0, 0, getWidth(), getHeight());
                    } else {
                        canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                    }
                }
            }

            if (mClipBounds != null) {
                // clip bounds ignore scroll
                canvas.clipRect(mClipBounds);
            }
        }

        if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // Fast path for layouts with no backgrounds
                //如果存在标志位PFLAG_SKIP_DRAW,则跳过本体的绘制,直接绘制其子view
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    //调用子View的draw方法,并将调整好的canvas传进去
                    draw(canvas);
                }
            }
        } else if (cache != null) {
            、、如果存在cache则直接利用cache
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
                // no layer paint, use temporary paint to draw bitmap
                Paint cachePaint = parent.mCachePaint;
                if (cachePaint == null) {
                    cachePaint = new Paint();
                    cachePaint.setDither(false);
                    parent.mCachePaint = cachePaint;
                }
                cachePaint.setAlpha((int) (alpha * 255));
                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            } else {
                // use layer paint to draw the bitmap, merging the two alphas, but also restore
                int layerPaintAlpha = mLayerPaint.getAlpha();
                if (alpha < 1) {
                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                }
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                if (alpha < 1) {
                    mLayerPaint.setAlpha(layerPaintAlpha);
                }
            }
        }

        //省略部分代码
        ......
    }

此方法与上文中的draw方法不同,主要思想是会先检测缓存,如果没有缓存则调再调用上文中的draw(Canvas canvas)方法进行绘制或者直接通知子view进行绘制。
第六步,绘制装饰物:

public void onDrawForeground(Canvas canvas) {
        //绘制滑动指示器
        onDrawScrollIndicators(canvas);
        //绘制滑动条
        onDrawScrollBars(canvas);
        
        //初始化foreground,并先顶起绘制范围
        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (foreground != null) {
            if (mForegroundInfo.mBoundsChanged) {
                mForegroundInfo.mBoundsChanged = false;
                final Rect selfBounds = mForegroundInfo.mSelfBounds;
                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

                if (mForegroundInfo.mInsidePadding) {
                    selfBounds.set(0, 0, getWidth(), getHeight());
                } else {
                    selfBounds.set(getPaddingLeft(), getPaddingTop(),
                            getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
                }

                final int ld = getLayoutDirection();
                Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                        foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
                foreground.setBounds(overlayBounds);
            }
            //对foreground进行绘制
            foreground.draw(canvas);
        }
    }

小结

之前学习之后,原以为能过较为轻松完成这篇总结笔记,开始写之后才发现之前学习时还是有一些地方没有完全弄懂,边记边学断断续续用了近一周的时间才完成这篇总结。
在写的过程中通过不断的查阅资料又发现了不少比较好的博文,并在文中进行了标注。
最后由于本人能力有限,如有谬误,请斧正,谢谢。

参考资料

Android走进Framework之AppCompatActivity.setContentView
Android Choreographer 源码分析
View绘制流程及源码解析(一)——performTraversals()源码分析
《Android开发艺术探索》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容