我在《Android視圖結構》這篇文章中已經描述了Activity
,Window
和View
在視圖架構方面的關係。前天,我忽然想到爲何在setContentView
中可以調用findViewById
函數?View
那時不是尚未被加載,測量,佈局和繪製啊。而後就搜索了相關的條目,發現findViewById
只須要在inflate
結束以後就能夠。因而,我整理了Activity生命週期和View的生命週期的關係,並再次作一下總結。java
爲了節約你的時間,本篇文章的主要內容爲:git
Activity的生命週期和它包含的View的生命週期的關係github
Activity初始化時View爲何會三次measure,兩次layout但只一次draw?windows
ViewRoot的初始化過程架構
我經過一個簡單的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
,onLayout
和onDraw
函數呢?
你們應該都知道,有些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嗎?那你就太天真了!咱們都知道,視圖結構中不只僅是DecorView
是FrameLayout
,還有其餘的須要兩次measure子視圖的ViewGroup
,若是每次都致使子視圖兩次measure,那效率就過低了。因此View
的measure
函數中有相關的機制來防止這種狀況。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ...... // 當FLAG_FORCE_LAYOUT位爲1時,就是當前視圖請求一次佈局操做 //或者當前當前widthSpec和heightSpec不等於上次調用時傳入的參數的時候 //才進行重新繪製。 if (forceLayout || !matchingSize && (widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec)) { ...... onMeasure(widthMeasureSpec, heightMeasureSpec); ...... } ...... }
源碼看到這裏,我幾乎失望的眼淚掉下來!沒辦法,只能再想其餘的方法來分析這個問題。
爲了分析函數調用的層級關係,我想到了斷點調試法。因而,我果斷在onMeasure
和onLayout
函數中設置了斷點,而後進行調試。
在《Android視圖架構》一文中,咱們知道ViewRoot
是DecorView
的父視圖,雖然它本身並非一個View
,可是它實現了ViewParent
的接口,Android正是經過它來實現整個視圖系統的初始化工做。而它的performTraversals
函數就是視圖初始化的關鍵函數。
對比兩次onMeasure
被調用時的函數調用幀,咱們能夠輕易發現ViewRootImpl
的performTraversals
函數中直接和間接的調用了兩次performMeasure
函數,從而致使了View
最開始的兩次measure過程。
而後在相同的performTraversals
函數中會調用performLayout
函數,從而致使View
進行一輪layout過程。
可是爲何此次performTraversals
並無觸發View
的draw過程呢?反而是View
又將從新進行一輪measure,layout過程以後才進行draw。
經過斷點調試,咱們發如今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
的相關內容,我以後要繼續學習相關內容!