View的三次measure,兩次layout和一次draw

 我在《Android視圖結構》這篇文章中已經描述了Activity,WindowView在視圖架構方面的關係。前天,我忽然想到爲何在setContentView中可以調用findViewById函數?View那時不是尚未被加載,測量,佈局和繪製啊。而後就搜索了相關的條目,發現findViewById只須要在inflate結束以後就能夠。因而,我整理了Activity生命週期和View的生命週期的關係,並再次作一下總結。java

 爲了節約你的時間,本篇文章的主要內容爲:git

  • Activity的生命週期和它包含的View的生命週期的關係github

  • Activity初始化時View爲何會三次measure,兩次layout但只一次draw?windows

  • ViewRoot的初始化過程架構

Activity的生命週期和View的生命週期

 我經過一個簡單的demo來驗證Activity生命週期方法和View的生命週期方法的調用前後順序。請看以下截圖ide

截圖

 在onCreate函數中,咱們一般都調用setContentView來設置佈局文件,此時Android系統就會讀取佈局文件,可是視圖此時並無加載到Window上,而且也沒有進入本身的生命週期。
 只有等到Activity進入resume狀態時,它所擁有的View纔會加載到Window上,並進行測量,佈局和繪製。因此咱們會發現相關函數的調用順序是:函數

  • onResume(Activity)佈局

  • onPostResume(Activity)學習

  • onAttachedToWindow(View)spa

  • onMeasure(View)

  • onMeasure(View)

  • onLayout(View)

  • onSizeChanged(View)

  • onMeasure(View)

  • onLayout(View)

  • onDraw(View)

 你們會發現,爲何onMeasure先調用了兩次,而後再調用onLayout函數,最後還有在依次調用onMeasure,onLayoutonDraw函數呢?

ViewGroup的measure

 你們應該都知道,有些ViewGroup可能會讓本身的子視圖測量兩次。好比說,父視圖先讓每一個子視圖本身測量,使用View.MeasureSpec.UNSPECIFIED,而後在根據每一個子視圖但願獲得的大小不超過父視圖的一些限制,就讓子視圖獲得本身但願的大小,不然就用其餘尺寸來從新測量子視圖。這一類的視圖有FrameLayout,RelativeLayout等。
 在《Android視圖結構》中,咱們已經知道Android視圖樹的根節點是DecorView,而它是FrameLayout的子類,因此就會讓其子視圖繪製兩次,因此onMeasure函數會先被調用兩次。

// FrameLayout的onMeasure函數,DecorView的onMeasure會調用這個函數。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    .....
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            ......
        }
    }
    ........
    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            ........
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

 你覺得到了這裏就能解釋通View初始化時的三次measure,兩次layout卻只一次draw嗎?那你就太天真了!咱們都知道,視圖結構中不只僅是DecorViewFrameLayout,還有其餘的須要兩次measure子視圖的ViewGroup,若是每次都致使子視圖兩次measure,那效率就過低了。因此Viewmeasure函數中有相關的機制來防止這種狀況。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ......
  // 當FLAG_FORCE_LAYOUT位爲1時,就是當前視圖請求一次佈局操做
  //或者當前當前widthSpec和heightSpec不等於上次調用時傳入的參數的時候
  //才進行重新繪製。
    if (forceLayout || !matchingSize &&
            (widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec)) {
            ......
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            ......
    }
    ......
}

 源碼看到這裏,我幾乎失望的眼淚掉下來!沒辦法,只能再想其餘的方法來分析這個問題。

斷點調試大法好

 爲了分析函數調用的層級關係,我想到了斷點調試法。因而,我果斷在onMeasureonLayout函數中設置了斷點,而後進行調試。

函數調用幀

 在《Android視圖架構》一文中,咱們知道ViewRootDecorView的父視圖,雖然它本身並非一個View,可是它實現了ViewParent的接口,Android正是經過它來實現整個視圖系統的初始化工做。而它的performTraversals函數就是視圖初始化的關鍵函數。
 對比兩次onMeasure被調用時的函數調用幀,咱們能夠輕易發現ViewRootImplperformTraversals函數中直接和間接的調用了兩次performMeasure函數,從而致使了View最開始的兩次measure過程。
 而後在相同的performTraversals函數中會調用performLayout函數,從而致使View進行一輪layout過程。
 可是爲何此次performTraversals並無觸發View的draw過程呢?反而是View又將從新進行一輪measure,layout過程以後才進行draw。

兩次performTraversals

 經過斷點調試,咱們發如今View初始化的過程當中,系統調用了兩次performTraversals函數,第一次performTraversals函數致使了View的前兩次的onMeasure函數調用和第一次的onLayout函數調用。後一次的performTraversals函數致使了最後的onMeasure,onLayout,和onDraw函數的調用。可是,第二次performTraversals爲何會被觸發呢?咱們研究一下其源碼就可知道。

private void performTraversals() {
    ......
    boolean newSurface = false;
    //TODO:決定是否讓newSurface爲true,致使後邊是否讓performDraw沒法被調用,而是從新scheduleTraversals
    if (!hadSurface) {
        if (mSurface.isValid()) {
            // If we are creating a new surface, then we need to
            // completely redraw it.  Also, when we get to the
            // point of drawing it we will hold off and schedule
            // a new traversal instead.  This is so we can tell the
            // window manager about all of the windows being displayed
            // before actually drawing them, so it can display then
            // all at once.
            newSurface = true;
                    .....
        }
    }
            ......
    if (!cancelDraw && !newSurface) {
        if (!skipDraw || mReportNextDraw) {
            ......
            performDraw();
        }
    } else {
        if (viewVisibility == View.VISIBLE) {
            // Try again
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }
    ......
}

 由源代碼能夠看出,當newSurface爲真時,performTraversals函數並不會調用performDraw函數,而是調用scheduleTraversals函數,從而再次調用一次performTraversals函數,從而再次進行一次測量,佈局和繪製過程。
 咱們由斷點調試能夠輕易看到,第一次performTraversals時的newSurface爲真,而第二次時是假。

斷點調試截圖

總結

 雖然我已經經過源碼得知View初始化時measure三次,layout兩次,draw一次的緣由,可是Android系統設計時,爲何要將整個初始化過程設計成這樣?我卻尚未明白,爲何當Surface爲新的時候,要推遲繪製,從新進行一輪初始化,這些可能都要涉及到Surface的相關內容,我以後要繼續學習相關內容!

相關文章
相關標籤/搜索