Android 源碼分析 - View的measure、layout、draw三大流程

??經(jīng)過兩個多月的框架源碼轟炸,感覺自己的腦子變得有點懵逼了。在這兩個多月里面,先后看了RxJava、OkHttp和Retrofit的源碼,并且將自己的理解寫成了博客,作為記錄;后續(xù)又看了EventBus和ButterKnife的源碼,本來都想寫成博客的,但是覺得這兩個框架比較簡單,因此就沒有寫(純粹個人想法,大家有意見的話,盡管噴);最后,就是簡單的看了一下Glide的源碼,太特么的難了,看不懂看不懂,看到一半就放棄了,應(yīng)該是自己的功力不夠,自己再沉淀沉淀,之后再去試試吧。
??今天,我將帶來一篇比較輕松的文章--View的mesure、layout、draw三大流程。本文將詳細講解View的三大流程,閱讀本文最好有牢固的Android基礎(chǔ),并且對Android View的基本結(jié)構(gòu)有所了解。
??說到寫本文的經(jīng)歷還有點曲折,本來一開始打算好好的寫這篇文章,但是寫著寫著感覺沒什么寫的,然后自己轉(zhuǎn)而去看RecyclerView的源碼,將RecyclerView的三大流程簡單的梳理完畢之后,發(fā)現(xiàn)RecyclerView的三大流程跟普通的View有很大的不同,所以決定重新來寫這篇文章。說到底,本文就是為了后面的RecyclerView源碼打基礎(chǔ)??。
??好了,廢話少說,進入正文。本文參考資料:
??1. Android View源碼解讀:淺談DecorView與ViewRootImpl
??2. Android View 測量流程(Measure)完全解析
??3. 從requestLayout()初探View的繪制原理
??4.Android View 繪制流程(Draw) 完全解析
??5. 任玉剛大神的《Android開發(fā)藝術(shù)探索》
??注意:本文所有源碼都基于 API 27。

1. 概述

??View的三大流程非常的重要,重要到那種程度呢?幾乎達到了面試必問的程度,同時,在實際的開發(fā)中,如果熟悉三大流程的話,自定義View可以寫的非常6,當(dāng)然在解決那種迷之問題時,熟悉三大流程必將事倍功半。
??View的三大流程,分別是measure、layout、draw三個過程。我想,不用解釋這三大流程分別是干嘛的吧?咱們從它的英文意思上就可以知道。
??在正式分析源碼之前,我們先來通過一張圖片對三大流程有一個整體的了解。


??上面的流程圖從大概上解釋了三大流程的過程,但是很多的細節(jié)都沒有解釋到,這就需要我們從源碼的程度來分析了。接下來,我們正式進入View三大流程的源碼分析。

2. ViewRootImpl

??View的三大流程從ViewRootImplperformTraversals方法開始的,具體是怎么調(diào)用的這個方法來的,這里就不詳細的解釋了,因為這里面涉及到Activity的創(chuàng)建、setContentViewPhoneWindow等等。這里我們只需要知道,performTraversals方法就是三大流程的開始。但是整個過程是怎么傳遞下去的呢?這個我們必須得對整個Activity的布局結(jié)構(gòu)有一個整體的認識,我們來看看。
??由于這部分的知識不是本文的核心內(nèi)容,所以這里就不貼出源碼來展示了。我就簡單的解釋一下。
??每一個Activity都一個Window對象的,Activity所有的View操作都托管給這個Window,我們可以把這個Window對象看成Activity的代理對象,包括ActivitysetContentViewfindViewById方法都是由Window接管的。所以,我們看到Activity的布局,實際上是Window的布局。
??同時,我們還知道,Android中的View成樹形結(jié)構(gòu),樹必須就得有一個根,那么在Window中,這個View樹的根是什么呢?沒錯,就是我們DectorView。而DectorView本身是一個FrameLayout,并沒有什么優(yōu)勢?所以通常在DectorView里面還會有一個類似于LinearLayout,這個LinearLayout裝著兩部分的布局,一部分是ActionBar,另一部分是contentView,也就是我們通過setContentView方法設(shè)置的布局那部分,contentView的id固定是android.R.id.content,這個在開發(fā)中有一定的幫助。
??而ViewRootImplperformTraversals方法就是DecorView的三大流程,然后借助DecorView將這三個流程傳遞下去,就像是事件分發(fā)機制一樣,一層一層傳遞下去。然后DecorView雖然是一個ViewGroup,但是它的三大流程跟普通的ViewGroup相比,有一定的差別。
??這里,我只是對Activity的布局基本介紹一下,具體的原理和底層的代碼我也不是很了解,所以也不好深入的分析這一塊,況且本文并不是分析這一塊的知識,所以,這里我就簡單的說明一下?,F(xiàn)在我們來開始對源碼進行分析,先來看看performTraversals方法相關(guān)代碼:

            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);
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    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;
                }
            }
        }
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            performLayout(lp, mWidth, mHeight);
        }
        if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            performDraw();
        }
   }

??performTraversals方法比較長,這里我只是將關(guān)鍵性代碼展示出來,在這里我們將知道三大流程的調(diào)用順序,最先是measure過程,通過performMeasure方法開始的;其次,layout過程通過performLayout方法開始;最后,draw過程通過performDraw方法開始的。接下來,我們簡單的看一下這三個方法。為什么簡單看一下呢?因為這三個方法就是操作分發(fā)到DecorView,過程是非常的簡單。

(1). 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);
        }
    }

??performMeasure方法里面幾乎沒做什么,就是把Measure操作傳遞到DecorView里面,而這列mView就是DecorView對象。

(2). performLayout

??performLayout方法比較長,這里就不詳細的分析整個過程,但是最終的結(jié)果就是調(diào)用了DecorViewlayout方法。待會我們在分析DecorView時,將會詳細的分析。

(3). performDraw

??performDraw方法跟performLayout方法一樣,最后調(diào)用DecorViewdraw方法,來繪制View。具體的細節(jié),之后我們會詳細的分析。這里我們先有一個概念就行。

3. meaure

??三大流程相互獨立,如果合在一起分析難免會繞圈子,所以打算一一的來分析,將每個流程單獨的打通。首先我們來看看measure流程。

(1).measure方法

??measure流程從ViewRootImplperformMeasure方法開始,調(diào)用了mView是什么呢?沒錯,就是DecorView。DecorViewmeasure方法時從View那里繼承過來的。同時,不僅僅是DecorView,所以控件的measure方法都是View那里繼承過來的,因為measure是一個final方法,不能重寫。接下來,我們來看看Viewmeasure方法:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        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;
        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) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
            resolveRtlPropertiesIfNeeded();
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
        }
    }

??Viewmeasure方法比較簡單,為了代碼簡潔,我省略了很多沒必要的代碼,我們只來看看核心代碼。整個measure方法流程,我們只需要記住一點,就是判斷調(diào)用onMeasure方法,其他的代碼都是來幫助達到這個目的的。
??我們來看看,什么時候需要調(diào)用onMeasure方法,什么時候又不需要調(diào)用onMeasure方法,而這種時候為什么不要調(diào)用onMeasure方法。這三個問題,是我們重點關(guān)心的。
??從代碼中看來,我們知道forceLayout為true或者needsLayout為true時,有可能會調(diào)用onMeasure方法。而這兩個方法有表示什么意思呢?
??forceLayout變量,我們從名字就知道是什么意思,判斷時候強制布局,這個非常理解?那這個變量在什么時候為true呢?從View的Api方法中,我們找到了一個方法--forceLayout方法。

   public void forceLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
    }

??在forceLayout方法里面,這里mPrivateFlags變量跟 PFLAG_FORCE_LAYOUT做了一個或的位運算,所以在measure方法里面,forceLayout才會為true:

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

??不過這里需要注意的是,如果View第一次調(diào)用measure方法,forceLayout是肯定為true的。具體是為什么,我也不太清楚,但是我們可以通過下面的代碼來驗證一下:

  public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    try {
      Field flags = this.getClass().getField("mPrivateFlags");
      flags.setAccessible(true);
      int anInt = flags.getInt(this);
      Log.i("pby123", " " + ((anInt & (0x00001000)) == (0x00001000)));
    } catch (NoSuchFieldException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }
  }

??下面就是log日志:


??所以我們可以得出一個結(jié)論,一個ViewonMeasure方法至少會被執(zhí)行一次。
??其實,我們可以這樣來想,如果在某些情況下onMeasure方法不會被執(zhí)行,那么我們在外部調(diào)用ViewgetMeasureWidth方法始終得到的是0,這是不可能的。同時,如果getMeasureWidth方法返回值為0的話,那么在layout階段,我們根本不知道怎么進行布局,這也是不可能的。這樣,我們就從側(cè)面可以得出,onMeasure方法至少會被執(zhí)行一次。
??那為什么需要判斷是否執(zhí)行onMeasure方法呢?這是為了避免多次執(zhí)行的onMeasure方法。
??另一個變量就是needsLayout,這個變量我們從名字上就可以判斷出來,表示是否需要布局,這個變量在什么時候為true呢?

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

??首先是判斷當(dāng)前的寬高是否老的寬高相同,如果相同,沒必要再次測量,同時如果當(dāng)前ViewmodeEXACTLYmatch_parent都沒必要測量。為什么在EXACTLYmatch_parent時,不要調(diào)用onMeasure測量呢?
??首先當(dāng)modeEXACTLY時,表示當(dāng)前View的寬高在第一次調(diào)用onMeasure方法已經(jīng)定死了,沒必要調(diào)用onMeasure方法進行測量。
??其次就是match_parent,跟EXACTLY一樣,在父View分發(fā)measure事件下來時,也是經(jīng)過第一次measure方法之后,寬高已經(jīng)定死了,后續(xù)就沒必要再次測量。
??將measure方法簡單的分析一下之后,我們來看看DecorViewonMeasure方法,看看怎么測量自己和測量子View的。

(2).onMeasure方法

??DecorViewonMeasure方法比較長,我先簡單將這個方法分為過程,然后一一來分析。

1.根據(jù)mode,來計算widthMeasureSpecheightMeasureSpec
2.如果存在outset,并且mode不為UNSPECIFIED,那么就會考慮到outset,重新計算widthMeasureSpecheightMeasureSpec
3.調(diào)用super.onMeasure方法,進行真正的測量。

??前兩步都沒有什么可以分析,都是基本的操作,相信熟悉Android測量規(guī)則的同學(xué)對此不會陌生。我們的重點在第三步里面。由于DecorView繼承于FrameLayout,所以,我們來看看FrameLayoutonMeasure方法。
??FrameLayoutonMeasure方法也比較長,這里先分為幾個過程。

  1. 調(diào)用每個child的measure方法,測量每個child的寬高;并且記錄設(shè)置了match_parent屬性的child
  2. 調(diào)用setMeasuredDimension方法,對自身寬高進行設(shè)置。
  3. 對設(shè)置了match_parent屬性的child進行測量。

??整個過程還是比較清晰,我們一個一個來分析。首先來看看第一個過程:


        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

??這個過程,我們可以將它分成3個部分來看:

1 . 調(diào)用measureChildWithMargins方法對子View的進行測量。

  1. 不斷更新maxHeightmaxWidth的值,主要是用于父View的測量,如果父View本身為wrap_content,這兩個值就非常的重要。
  2. 記錄下設(shè)置match_parent屬性的child,當(dāng)父View的寬高確定之后,在進行第二次測量。

??2和3我們都不用看了,重點來看看measureChildWithMargins方法。還記得在很久很久以前,我就分析過這個方法,有興趣的同學(xué)可以去看看:Android 踩坑系列-ViewGroup的子View真正實現(xiàn)Margin屬性。好了,我們來看看measureChildWithMargins方法:

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

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

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

??measureChildWithMargins方法比較簡單,就是通過調(diào)用getChildMeasureSpec方法來獲取child的MeasureSpec,然后將計算完畢的MeasureSpec傳遞到childmeasure進行真正測量。這里的重點在getChildMeasureSpec方法,也是整個Android系統(tǒng)中的View測量核心之一,從這個方法里面,我們可以獲得很多的測量規(guī)則。我們重點分析分析:

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

??在分析這個方法之前,我們先對每個變量有一個認識。

變量名 類型 含義
spec int ViewMeasureSpec,在getChildMeasureSpec方法里面,主要是通過這個變量來獲得父View的測量mode。因為子ViewMeasureSpec是由父ViewMeasureSpec和子ViewMeasureSpec共同決定的
padding int 主要是記錄父Viewpadding和子Viewmargin
childDimension int ViewMeasureSpec,與spec共同決定子ViewMeasureSpec

??整個getChildMeasureSpec方法比較簡單,分為三種大情況,每種大情況又分為三種小情況,所以一共9種情況。現(xiàn)在我們通過一張表來分析。


??上面表中就詳細的分析了每種情況下規(guī)則,這里我就不多說了。
??通過getChildMeasureSpec方法,我們可以獲得childMeasureSpec,然后調(diào)用childmeasure方法進行測量,這就將measure事件分發(fā)下去了
??對第一個過程分析完畢之后,我們來看第二個過程:調(diào)用setMeasuredDimension方法,對自身寬高進行設(shè)置。

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

??這一步比較簡單,通過resolveSizeAndState方法來獲得父ViewMeasureSpec。這里主要是考慮到父View可能是warp_content,所以有maxHeightmaxWidth參與,這里就不分析resolveSizeAndState方法了,有興趣的同學(xué)可以看看。
??最后就是測量設(shè)置了match_parentchild,這個過程跟第一個過程比較像,這里就在就不分析了。
??整個measure過程,我們算是分析完畢了。這里我做一個簡單的總結(jié)。

  1. measure過程從DecorViewmeasure方法開始,而measure本身不會進行測量,而是分發(fā)到了onMeasure方法。由于DecorView繼承于
    FrameLayout,所以調(diào)用的是FrameLayoutonMeasure方法。
  2. FrameLayoutonMeasure方法會測量自身,同時同時會將測量事件分發(fā)到每個View手里,從而完成了整個View樹的測量。

??分析完畢measure過程,現(xiàn)在我們來看看layout過程。

4. layout

??前面已經(jīng)說了,ViewRootImpl會通過performLayout方法來分發(fā),而performLayout方法最終會調(diào)用DecorViewlayout方法進行布局。
??我們先來看看performLayout方法:

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

        final View host = mView;
        if (host == null) {
            return;
        }

        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                // requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    // Set this flag to indicate that any further requests are happening during
                    // the second pass, which may result in posting those requests to the next
                    // frame instead
                    mHandlingLayoutInLayoutRequest = true;

                    // Process fresh layout requests, then measure and layout
                    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");
                        view.requestLayout();
                    }
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;

                    // Check the valid requests again, this time without checking/clearing the
                    // layout flags, since requests happening during the second pass get noop'd
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // Post second-pass requests to the next 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();
                                }
                            }
                        });
                    }
                }

            }
        }
    }

??整個performLayout方法比較長,我將它分為兩個部分。

  1. 如果host不為null,也就是DecorView不為null,調(diào)用DecorViewlayout方法,將布局操作分發(fā)下去。
  2. 如果mLayoutRequesters不為空的話,進行第二次布局。至于mLayoutRequesters什么不為空,這就涉及到requestLayout方法了,后續(xù)我會單獨寫一篇文章來分析這個方法,本文不做過多的講解。

??這里,我們重點的看第一個部分。第一個部分調(diào)用了DecorViewlayout方法。而DecorViewlayout方法最終會調(diào)用到Viewlayout方法,我們直接來看Viewlayout方法:

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                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);

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

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            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;

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

??Viewlayout方法也比較簡單,我將它分為兩個部分:

  1. 調(diào)用onLayout方法,進行真正的布局操作。
  2. 回調(diào)OnLayoutChangeListeneronLayoutChange方法,告訴觀察者當(dāng)前的布局已經(jīng)改變了。

??第二部分沒有分析的必要,這個相信大多數(shù)的同學(xué)已經(jīng)司空見慣了。我們重點來看看onLayout方法,而ViewonLayout方法本身是一個空方法。從這個空方法,我們可以得出兩點結(jié)論:

  1. 普通的View調(diào)用layout方法進行布局,其實就是簡單將left、top、right、bottom4個變量記錄下來,并沒有做其他的操作布局。
  2. ViewGroup必須實現(xiàn)onLayout方法,制定子View的布局規(guī)則。這就是ViewGroup有一個抽象方法的原因。

??既然Viewlayout調(diào)用了onLayout方法,接下來我們來看看DecorViewonLayout方法

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        getOutsets(mOutsets);
        if (mOutsets.left > 0) {
            offsetLeftAndRight(-mOutsets.left);
        }
        if (mOutsets.top > 0) {
            offsetTopAndBottom(-mOutsets.top);
        }
        if (mApplyFloatingVerticalInsets) {
            offsetTopAndBottom(mFloatingInsets.top);
        }
        if (mApplyFloatingHorizontalInsets) {
            offsetLeftAndRight(mFloatingInsets.left);
        }

        // If the application changed its SystemUI metrics, we might also have to adapt
        // our shadow elevation.
        updateElevation();
        mAllowUpdateElevation = true;

        if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
            getViewRootImpl().requestInvalidateRootRenderNode();
        }
    }

??DecorViewonLayout方法,我也簡單將它分為兩步:

  1. 調(diào)用super.onLayout方法,也就是FrameLayoutonLayout方法來進行布局。
  2. 根據(jù)mOutsets來調(diào)整位置。至于mOutsets是什么,抱歉,我也不知道??。

??看來我們看看FrameLayoutonLayout方法。

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

??好嘛,又調(diào)用layoutChildren方法。在layoutChildren方法里面才是真正對child進行布局的操作。
??這里就不對layoutChildren方法進行展開了,因為比較簡單。就是根據(jù)每種ViewGroup不同的布局特性,進行計算每個view的left、top、right和bottom,然后調(diào)用childlayout方法。
??如果child是一個普通的View的話,那么調(diào)用layout方法就是記錄下4個值,等待draw流程的到來;如果child是一個ViewGroup的話,就會像FrameLayout一樣,將layout事件分發(fā)下去。
??如上,就是整個View的layout流程,這里我做一個簡單的總結(jié)。

  1. layout過程從DecorViewlayout方法(也是Viewlayout方法)開始。在Viewlayout方法里面,會記錄下自身的left、top、right、bottom4個屬性,等待繪制,同時會調(diào)用onLayout方法將layout事件分發(fā)下去。
  2. 如果是普通的View,在layout方法里面調(diào)用onLayout方法是沒有用的,因為在View里面,onLayout方法是一個空方法;如果是一個ViewGroup,在onLayout里面,會調(diào)用每個childlayout方法。這樣整個layout流程就走通了。

??分析完layout流程之后,我們再來看看三大流程的最后一個流程--draw。

5. draw

??前面已經(jīng)說了,View樹的draw操作是從ViewRootImplperformDraw方法開始的。現(xiàn)在我們來看看performDraw方法。

    private void performDraw() {
        // ······
        try {
            draw(fullRedrawNeeded);
        } finally {
        }
        // ······
    }

??performDraw方法比較長,這里我將代碼簡化了一下。說到底,performDraw方法就是調(diào)用draw方法。
??我們來看一下draw方法,整個draw方法比較長,我簡單的將它分為幾個部分:

  1. 根據(jù)fullRedrawNeeded變量,來計算dirtydirty是一個矩陣,表示這次繪制的范圍。
  2. 調(diào)用drawSoftware方法進行繪制。

??整個draw方法比較復(fù)雜,因為這里面涉及到動畫之類的。如果此時在動畫,表示本次繪制并不是最終的繪制,所以需要調(diào)用scheduleTraversals方法往主線程post一個Message用來下次繪制。
??其次,dirty的計算也是比較復(fù)雜的,我們這里也不去分析,因為這些都是計算,如果深入分析的話,容易將我們聰明的大腦搞暈??。
??我們還是直接來看drawSoftware方法吧。

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        final Canvas canvas;
        try {
            canvas = mSurface.lockCanvas(dirty);
            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            return false;
        }

        try {
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }

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

            try {
                canvas.translate(-xoff, -yoff);
                mView.draw(canvas);

            } finally {
            }
        } finally {
            try {
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                return false;
            }

        }
        return true;
    }

??整個drawSoftware方法比較長,我簡化了一下代碼。這里,我先將整個方法分為3個部分:

  1. 根據(jù)dirty矩陣獲得繪制的Canvas對象
  2. 調(diào)用DecorViewdraw方法,繪制整個View
  3. 釋放Canvas

??我們一一的分析,首先來看看第一步。

            canvas = mSurface.lockCanvas(dirty);
            // TODO: Do this in native
            canvas.setDensity(mDensity);

??這里通過mSurface來鎖定一塊畫布,從而保證后續(xù)的繪制操作是線程安全的。
??與之對應(yīng)的是,最后是釋放了這塊區(qū)域。
??我們重點的是是如下的代碼:

                mView.draw(canvas);

??上面的代碼最終是調(diào)用Viewdraw方法。我們來看看Viewdraw方法:

    public void draw(Canvas canvas) {
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;
        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) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);
            // Step 4, draw the children
            dispatchDraw(canvas);
            drawAutofilledHighlight(canvas);
            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }
            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);
            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);
            return;
        }
        //······
    }

??整個draw方法的流程非常的清晰,一個分為7步:

  1. 調(diào)用drawBackground方法,繪制背景。
  2. 保存當(dāng)前View的畫布層次,這一步只在繪制fading edge才會執(zhí)行。
  3. 調(diào)用onDraw方法,繪制View自身。
  4. 調(diào)用dispatchDraw方法,繪制children
  5. 繪制fading edge,這個只在View本身需要繪制ading edge才會執(zhí)行。
  6. 調(diào)用onDrawForeground方法,繪制View的前景。
  7. 調(diào)用drawDefaultFocusHighlight方法,繪制高亮部分。

??View通過這7步就將整個View樹繪制完畢。這里,我們就不對每個過程做詳細的分析,因為每個過程都可以寫的非常多,況且,我也不知道??。
??說到draw流程,就會想到invalidatepostInvalidate這兩個吊的一逼的方法,后續(xù)我會專門寫文章來分析這兩個方法,這里就不糾結(jié)了。
??draw流程算是分析完畢了,這里我對整個draw做一個小小的總結(jié)。

  1. draw流程是從ViewRootImplperformDraw方法開始,在這個方法主要是調(diào)用draw方法來進行操作。
  2. ViewRootImpldraw方法主要是做了兩步,一是計算畫布區(qū)域,用于后面獲取畫布對象;二是調(diào)用drawSoftware方法來進行操作。
  3. drawSoftware方法主要做了3步,一是獲得鎖定一個畫布對象;二是調(diào)用Viewdraw啟動整個draw流程的執(zhí)行;三是釋放畫布對象。
  4. Viewdraw方法一共分為7步。每步做了可以參考上面的說明,這里就不重復(fù)的介紹了。對于Viewdraw方法,我們沒必要去沒比較去糾結(jié)每步是怎么做的,因為這樣容易導(dǎo)致深入源碼,不可自拔。

6. 總結(jié)

??View三大流程的流程到這里算是已經(jīng)結(jié)束,總的來說,介紹比較粗糙。但是我們分析源碼,沒必要去糾結(jié)每一行代碼,搞懂整個流程就OK,因為整個Android framework架構(gòu)是非常的復(fù)雜。
??這里我對三大流程做一個簡單的總結(jié)。

  1. 三大流程從View都是從ViewRootImplperformTraversals方法,分別調(diào)用performMeasure、performLayoutperformDraw方法進行三大流程的分發(fā)。
  2. 三大流程的執(zhí)行流程非常的相似,都是一種View樹的遞歸遍歷思想。

??三大流程的源碼分析到此就結(jié)束了,接下來我會趁熱打鐵,進一步的分析requestLayoutinvalidatepostInvalidate這三個方法。因為這三個方法跟layout和draw兩個流程有關(guān)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。