在前面一篇文章中,我們分析了Android應用程序窗口的繪圖表面的創建過程。Android應用程序窗口的繪圖表面在創建完成之後,我們就可以從上到下地繪製它裏面的各個視圖了,即各個UI元素了。不過在繪製這些UI元素之前,我們還需要從上到下地測量它們實際所需要的大小,以及對它們的位置進行合適的安排,即對它們進行合適的佈局。在本文中,我們就將詳細地分析Android應用程序窗口的測量、佈局以及繪製過程。
從前面Android應用程序與SurfaceFlinger服務的關係概述和學習計劃這一系列的文章可以知道,Android應用程序窗口請求SurfaceFlinger服務創建了一個繪圖表面之後,就可以接着請求爲該繪圖表面創建圖形緩衝區,而當Android應用程序窗口往這些圖形緩衝區填充好UI數據之後,就可以請求SurfaceFlinger服務將它們渲染到硬件幀緩衝區中去,這樣我們就可以看到應用程序窗口的UI了。
Android應用程序窗口一般不會直接去操作分配給它的圖形緩衝區,而是通過一些圖形庫API來操作。例如,在前面Android系統的開機畫面顯示過程分析一文中,使用C++來開發的開機動畫應用程序bootanimation,它是通過OpenGL提供的API來繪製UI的。對於使用Java來開發的Android應用程序來說,它們一般是使用Skia圖形庫提供的API來繪製UI的。在Skia圖庫中,所有的UI都是繪製在畫布(Canvas)上的,因此,Android應用程序窗口需要將它的圖形緩衝區封裝在一塊畫布裏面,然後纔可以使用Skia庫提供的API來繪製UI。
我們知道,一個Android應用程序窗口裏麪包含了很多UI元素,這些UI元素是以樹形結構來組織的,即它們存在着父子關係,其中,子UI元素位於父UI元素裏面,因此,在繪製一個Android應用程序窗口的UI之前,我們首先要確定它裏面的各個子UI元素在父UI元素裏面的大小以及位置。確定各個子UI元素在父UI元素裏面的大小以及位置的過程又稱爲測量過程和佈局過程。因此,Android應用程序窗口的UI渲染過程可以分爲測量、佈局和繪製三個階段,如圖1所示:
圖1 Android應用程序窗口渲染三步曲
從前面Android應用程序窗口(Activity)的視圖對象(View)的創建過程分析一文可以知道,Android應用程序窗口的頂層視圖是一個類型爲DecorView的UI元素,而從前面Android應用程序窗口(Activity)的繪圖表面(Surface)的創建過程分析一文的Step 3又可以知道,這個頂層視圖最終是由ViewRoot類的成員函數performTraversals來啓動測量、佈局和繪製操作的,這三個操作分別由DecorView類的成員函數measure和layout以及ViewRoot類的成員函數draw來實現的。
接下來,我們就分別從DecorView類的成員函數measure和layout以及ViewRoot類的成員函數draw開始,分析Android應用程序窗口的測量、佈局和繪製過程。
1. Android應用程序窗口的測量過程
DecorView類的成員函數measure是從父類View繼承下來的,因此,我們就從View類的成員函數measure開始分析應用程序窗口的測量過程,如圖2所示:
圖2 Android應用程序窗口的測量過程
這個過程可以分爲3個步驟,接下來我們就詳細分析每一個步驟。
Step 1. View.measure
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... int mPrivateFlags; ...... int mOldWidthMeasureSpec = Integer.MIN_VALUE; ...... int mOldHeightMeasureSpec = Integer.MIN_VALUE; ...... public final void measure(int widthMeasureSpec, int heightMeasureSpec) { if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags &= ~MEASURED_DIMENSION_SET; ...... // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { throw new IllegalStateException(\\\"onMeasure() did not set the\\\" + \\\" measured dimension by calling\\\" + \\\" setMeasuredDimension()\\\"); } mPrivateFlags |= LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; } ...... }
這個函數定義在文件frameworks/base/core/java/android/view/View.java中。
參數widthMeasureSpec和heightMeasureSpec用來描述當前正在處理的視圖可以獲得的最大寬度和高度。對於應用程序窗口的頂層視圖來說,我們也可以認爲這兩個參數是用來描述應用程序窗口的寬度和高度。
ViewRoot類的成員變量mPrivateFlags的類型爲int,如果它的某一個位的值不等於0,那麼就隱含着當前視圖有一個相應的操作在等待執行中。ViewRoot類的另外兩個成員變量mOldWidthMeasureSpec和mOldHeightMeasureSpec用來保存當前視圖上一次可以獲得的最大寬度和高度。
當ViewRoot類的成員變量mPrivateFlags的FORCE_LAYOUT位不等於0時,就表示當前視圖正在請求執行一次佈局操作,這時候函數就需要重新測量當前視圖的寬度和高度。此外,當參數widthMeasureSpec和heightMeasureSpec的值不等於ViewRoot類的成員變量mldWidthMeasureSpec和mOldHeightMeasureSpec的值時,就表示當前視圖上一次可以獲得的最大寬度和高度已經失效了,這時候函數也需要重新測量當前視圖的寬度和高度。
當View類的成員函數measure決定要重新測量當前視圖的寬度和高度之後,它就會首先將成員變量mPrivateFlags的MEASURED_DIMENSION_SET位設置爲0,接着再調用另外一個成員函數onMeasure來真正執行測量寬度和高度的操作。View類的成員函數onMeasure執行完成之後,需要再調用另外一個成員函數setMeasuredDimension來將測量好的寬度和高度設置到View類的成員變量mMeasuredWidth和mMeasuredHeight中,並且將成員變量mPrivateFlags的EASURED_DIMENSION_SET位設置爲1。這個操作是強制的,因爲當前視圖最終就是通過View類的成員變量mMeasuredWidth和mMeasuredHeight來獲得它的寬度和高度的。爲了保證這個操作是強制的,View類的成員函數measure再接下來就會檢查成員變量mPrivateFlags的EASURED_DIMENSION_SET位是否被設置爲1了。如果不是的話,那麼就會拋出一個類型爲IllegalStateException的異常來。
View類的成員函數measure最後就會把參數widthMeasureSpec和heightMeasureSpec的值保存在成員變量mldWidthMeasureSpec和mOldHeightMeasureSpec中,以便可以記錄當前視圖上一次可以獲得的最大寬度和高度。
View類的成員函數onMeasure一般是由其子類來重寫的。例如,對於用來應用程序窗口的頂層視圖的DecorView類來說,它是通過父類FrameLayout來重寫祖父類View的成員函數onMeasure的。因此,接下來我們就分析FrameLayout類的成員函數onMeasure的實現。
Step 2. rameLayout.onMeasure
public class FrameLayout extends ViewGroup { ...... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; // Find rightmost and bottommost child for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); maxWidth = Math.max(maxWidth, child.getMeasuredWidth()); maxHeight = Math.max(maxHeight, child.getMeasuredHeight()); } } // Account for padding too maxWidth += mPaddingLeft + mPaddingRight + mForegroundPaddingLeft + mForegroundPaddingRight; maxHeight += mPaddingTop + mPaddingBottom + mForegroundPaddingTop + mForegroundPaddingBottom; // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground\\\'s minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec)); } ...... }
這個函數定義在文件frameworks/base/core/java/android/widget/FrameLayout.java中。
FrameLayout類是從ViewGroup類繼承下來的,後者用來描述一個視圖容器,它有一個類型爲View的數組mChildren,裏面保存的就是它的各個子視圖。ViewGroup類所供了兩個成員函數getChildCount和getChildAt,它們分別用來獲得一個視圖容器所包含的子視圖的個數,以及獲得每一個子視圖。
FrameLayout類的成員函數onMeasure首先是調用另一個成員函數measureChildWithMargins來測量每一個子視圖的寬度和高度,並且找到這些子視圖的最大寬度和高度值,保存在變量maxWidth和maxHeight 中。
FrameLayout類的成員函數onMeasure接着再將前面得到的寬度maxWidth和高度maxHeight分別加上當前視圖所設置的Padding值,其中,(mPaddingLeft,mPaddingRight,mPaddingTop,mPaddingBottom )表示當前視圖的內容區域的左右上下四條邊分別到當前視圖的左右上下四條邊的距離,它們是父類View的四個成員變量,(mForegroundPaddingLeft,mForegroundPaddingRight,mForegroundPaddingTop,mForegroundPaddingBottom)表示當前視圖的各個子視圖所圍成的區域的左右上下四條邊到當前視視的前景區域的左右上下四條邊的距離。從這裏就可以看出,當前視圖的內容區域的大小就等於前景區域的大小,而前景區域的大小大於等於各個子視圖的所圍成的區域,這是因爲前景區域本來就是用來覆蓋各個子視圖所圍成的區域的。
加上各個Padding值之後,得到的寬度maxWidth和高度maxHeight還不是最終的寬度和高度,還需要考慮以下兩個因素:
1. 當前視圖是否設置有最小寬度和高度。如果設置有的話,並且它們比前面計算得到的寬度maxWidth和高度maxHeight還要大,那麼就將它們作爲當前視圖的寬度和高度值。
2. 當前視圖是否設置有前景圖。如果設置有的話,並且它們比前面計算得到的寬度maxWidth和高度maxHeight還要大,那麼就將它們作爲當前視圖的寬度和高度值。
經過上述兩步檢查之後,FrameLayout類的成員函數onMeasure就得到了當前視圖的寬度maxWidth和高度maxHeight。由於得到的寬度和高度又必須要限制在參數widthMeasureSpec和heightMeasureSpec所描述的寬度和高度規範之內,因此,FrameLayout類的成員函數onMeasure就會調用從View類繼承下來的成員函數resolveSize來獲得正確的大小。得到了當前視圖的正確大小之後,FrameLayout類的成員函數onMeasure就可以調用從父類View繼承下來的成員函數setMeasuredDimension來將它們爲當前視圖的大小了。
爲了理解參數widthMeasureSpec和heightMeasureSpec的含義,我們繼續分析View類的成員函數resolveSize的實現,如下所示:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... public static int resolveSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: result = Math.min(size, specSize); break; case MeasureSpec.EXACTLY: result = specSize; break; } return result; } ...... }
這個函數定義在文件rameworks/base/core/java/android/view/View.java中。
參數measureSpec的值其實是由兩部分內容來組成的,最高2位表示一個測量規範,而低30位表示一個寬度值或者高度值。測量規範有三種,分別是0、1和2,使用常量MeasureSpec.UNSPECIFIED、MeasureSpec.EXACTLY和MeasureSpec.AT_MOST來表示。
當參數measureSpec描述的規範是MeasureSpec.UNSPECIFIED時,就表示當前視圖沒有指定它的大小測量模式,這時候就使用參數size的值;當參數measureSpec描述的規範是MeasureSpec.AT_MOST時,就表示當前視圖的大小等於參數size和參數measureSpec所指定的值中的較小值;當參數measureSpec描述的規範是MeasureSpec.EXACTLY時,就表示當前視圖的大小等於參數measureSpec中所指定的值。
回到FrameLayout類的成員函數onMeasure中,我們再來看一下View類的成員函數setMeasuredDimension是如何設置當前視圖的大小的,如下所示:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= MEASURED_DIMENSION_SET; } ...... }
這個函數定義在文件rameworks/base/core/java/android/view/View.java中。
View類的成員函數setMeasuredDimension首先將參數measuredWidth和measuredHeight的值保存在成員變量mMeasuredWidth和mMeasuredHeight中,用來作爲當前視圖的寬度和高度,並且將成員變量mPrivateFlags的位MEASURED_DIMENSION_SET設置爲1,這樣返回到前面的Step 1時,就不會拋出一個類型爲IllegalStateException的異常了。
FrameLayout類的另一個成員函數measureChildWithMargins是從父類ViewGroup繼承下來的,接下來我們就繼續分析它的實現,以便可以瞭解一個視圖容器的各個子視圖的大小的測量過程。
Step 3. ViewGroup.measureChildWithMargins
public abstract class ViewGroup extends View implements ViewParent, ViewManager { ...... 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); } ...... }
這個函數定義在文件rameworks/base/core/java/android/view/ViewGroup.java中。
參數child用來描述當前要測量大小的子視圖,參數parentWidthMeasureSpec和parentHeightMeasureSpec用來描述當前子視圖可以獲得的最大寬度和高度,參數widthUsed和heightUsed用來描述父窗口已經使用了的寬度和高度。ViewGroup類的成員函數measureChildWithMargins必須要綜合考慮上述參數,以及當前正在測量的子視圖child所設置的大小和Margin值,還有當前視圖容器所設置的Padding值,來得到當前正在測量的子視圖child的正確寬度childWidthMeasureSpec和高度childHeightMeasureSpec,這是通過調用ViewGroup類的另外一個成員函數getChildMeasureSpec來實現的。
得到了當前正在測量的子視圖child的正確寬度childWidthMeasureSpec和高度childHeightMeasureSpec之後,就可以調用它的成員函數measure來設置它的大小了,即執行前面Step 1的操作。注意,如果當前正在測量的子視圖child描述的也是一個視圖容器,那麼它又會重複執行Step 2和Step 3的操作,直到它的所有子孫視圖的大小都測量完成爲止。
至此,我們就分析完成Android應用程序窗口的測量過程了,接下來我們繼續分析Android應用程序窗口的佈局過程。
2. Android應用程序窗口的佈局過程
DecorView類的成員函數layout是從父類View繼承下來的,因此,我們就從View類的成員函數layout開始分析應用程序窗口的佈局過程,如圖3所示:
圖3 Android應用程序窗口的佈局過程
這個過程可以分爲5個步驟,接下來我們就詳細地分析每一個步驟。
Step 1. View.layout
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... int mPrivateFlags; ...... public final void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { ...... onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; } ...... }
這個函數定義在文件frameworks/base/core/java/android/view/View.java中。
參數l、t、r和b分別用來描述當前視圖的左上右下四條邊與其父視圖的左上右下四條邊的距離,這樣當前視圖通過這四個參數就可以知道它在父視圖中的位置以及大小。
View類的成員函數layout首先調用另外一個成員函數setFrame來設置當前視圖的位置以及大小。設置完成之後,如果當前視圖的大小或者位置與上次相比發生了變化,那麼View類的成員函數setFrame的返回值changed就會等於true。在這種情況下, View類的成員函數layout就會繼續調用另外一個成員函數onLayout重新佈局當前視圖的子視圖。此外,如果此時View類的成員變量mPrivateFlags的LAYOUT_REQUIRED位不等於0,那麼也表示當前視圖需要重新佈局它的子視圖,因此,這時候View類的成員函數layout也會調用另外一個成員函數onLayout。
當前視圖的子視圖都重新佈局完成之後,View類的成員函數layout就可以將成員變量mPrivateFlags的LAYOUT_REQUIRED位設置爲0了,因爲此時當前視圖及其子視圖都已經執行了一次佈局操作了。
View類的成員函數layout最後還會將成員變量mPrivateFlags的FORCE_LAYOUT位設置爲0,也是因爲此時當前視圖及其子視圖的佈局已經是最新的了。
接下來,我們就繼續分析View類的成員函數setFrame和onLayout的實現,以便可以瞭解當前視圖及其子視圖是如何執行佈局操作的。
Step 2. View.setFrame
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... int mPrivateFlags; ...... int mViewFlags; ...... protected int mLeft; ...... protected int mRight; ...... protected int mTop; ...... protected int mBottom; ...... private boolean mBackgroundSizeChanged; ...... 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 & DRAWN; // Invalidate our old position invalidate(); int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; mLeft = left; mTop = top; mRight = right; mBottom = bottom; mPrivateFlags |= HAS_BOUNDS; int newWidth = right - left; int newHeight = bottom - top; if (newWidth != oldWidth || newHeight != oldHeight) { onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, therby clearing // the DRAWN bit. mPrivateFlags |= DRAWN; invalidate(); } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; } return changed; } ...... }
這個函數定義在文件frameworks/base/core/java/android/view/View.java中。
View類的成員變量mLeft、mRight、mTop和mBottom分別用來描述當前視圖的左右上下四條邊與其父視圖的左右上下四條邊的距離,如果它們的值與參數left、right、top和bottom的值不相等,那麼就說明當前視圖的大小或者位置發生變化了。這時候View類的成員函數setFrame就需要將參數left、right、top和bottom的值分別記錄在成員變量mLeft、mRight、mTop和mBottom中。在記錄之前,還會執行兩個操作:
1. 將成員變量mPrivateFlags的DRAWN位記錄在變量drawn中,並且調用另外一個成員函數invalidate來檢查當前視圖上次請求的UI繪製操作是否已經執行。如果已經執行了的話,那麼就會再請求執行一個UI繪製操作,以便可以在修改當前視圖的大小和位置之前,將當前視圖在當前位置按照當前大小顯示一次。在接下來的Step 3中,我們再詳細分析View類的成員函數invalidate的實現。
2. 計算當前視圖上一次的寬度oldWidth和oldHeight,以便接下來可以檢查當前視圖的大小是否發生了變化。
當前視圖距離父視圖的邊距一旦設置好之後,它就是一個具有邊界的視圖了,因此,View類的成員函數setFrame接着還會將成員變量mPrivateFlags的HAS_BOUNDS設置爲1。
View類的成員函數setFrame再接下來又會計算當前視圖新的寬度newWidth和高度newHeight,如果它們與上一次的寬度oldWidth和oldHeight的值不相等,那麼就說明當前視圖的大小發生了變化,這時候就會調用另外一個成員函數onSizeChanged來讓子類有機會處理這個變化事件。
View類的成員函數setFrame接下來繼續判斷當前視圖是否是可見的,即成員變量mViewFlags的VISIBILITY_MASK位的值是否等於VISIBLE。如果是可見的話,那麼就需要將成員變量mPrivateFlags的DRAWN位設置爲1,以便接下來可以調用另外一個成員函數invalidate來成功地執行一次UI繪製操作,目的是爲了將當前視圖馬上顯示出來。
View類的成員變量mPrivateFlags的DRAWN位描述的是當前視圖上一次請求的UI繪製操作是否已經執行過了。如果它的值等於1,就表示已經執行過了,否則的話,就表示還沒在等待執行。前面第一次調用View類的成員函數invalidate來檢查當前視圖上次請求的UI繪製操作是否已經執行時,如果發現已經執行了,那麼就會重新請求執行一次新的UI繪製操作,這時候會導致當前視圖的成員變量mPrivateFlags的DRAWN位重置爲0。注意,新請求執行的UI繪製只是爲了在修改當前視圖的大小以及大小之前,先將它在上一次設置的大小以及位置中繪製出來,這樣就可以使得當前視圖的大小以及位置出現平滑的變換。換句話說,新請求執行的UI繪製只是爲了獲得一箇中間效果,它不應該影響當前視圖的繪製狀態,即不可以修改當前視圖的成員變量mPrivateFlags的DRAWN位。因此,我們就需要在前面第一次調用View類的成員函數invalidate前,先將當前視圖的成員變量mPrivateFlags的DRAWN位保存下來,即保存在變量drawn中,然後等到調用之後,再將變量drawn的值恢復到當前視圖的成員變量mPrivateFlags的DRAWN位中去。
另一方面,如果當前視圖的大小和位置發生了變化,View類的成員函數setFrame還會將成員變量mBackgroundSizeChanged的值設置爲true,以便可以表示當前視圖的背景大小發生了變化。
最後,View類的成員函數setFrame將變量changed的值返回給調用者,以便調用者可以知道當前視圖的大小和位置是否發生了變化。
接下來,我們繼續分析View類的成員函數invalidate的實現,以便可以瞭解當前視圖是如何執行一次UI繪製操作的。
Step 3. View.invalidate
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... protected ViewParent mParent; ...... int mPrivateFlags; ...... public void invalidate() { ...... if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) { mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID; final ViewParent p = mParent; final AttachInfo ai = mAttachInfo; if (p != null && ai != null) { final Rect r = ai.mTmpInvalRect; r.set(0, 0, mRight - mLeft, mBottom - mTop); // Don\\\'t call invalidate -- we don\\\'t want to internally scroll // our own bounds p.invalidateChild(this, r); } } } ...... }
這個函數定義在文件frameworks/base/core/java/android/view/View.java中。
View類的成員函數invalidate首先檢查成員變量mPrivateFlags的DRAWN位和HAS_BOUNDS位是否都被設置爲1。如果是的話,那麼就說明當前視圖上一次請求執行的UI繪製操作已經執行完成了,這時候View類的成員函數invalidate纔可以請求執行新的UI繪製操作。
View類的成員函數invalidate在請求新的UI繪製操作之前,會將成員變量mPrivateFlags的DRAWN位和DRAWING_CACHE_VALID位重置爲0,其中,後者表示當前視圖正在緩存的一些繪圖對象已經失效了,這是因爲接下來就要重新開始繪製當前視圖的UI了。
請求繪製當前視圖的UI是通過調用View類的成員變量mParent所描述的一個ViewParent接口的成員函數invalidateChild來實現的。前面我們假設當前視圖是應用程序窗口的頂層視圖,即它是一個類型爲DecoreView的視圖,它的成員變量mParent指向的是與其所關聯的一個ViewRoot對象。因此,繪製當前視圖的UI的操作實際上是通過調用ViewRoot類的成員函數invalidateChild來實現的。
注意,在調用ViewRoot類的成員函數invalidateChild的成員函數invalidateChild來繪製當前視圖的UI之前,會將當前視圖即將要繪製的區域記錄在View類的成員變量mAttachInfo所描述的一個AttachInfo對象的成員變量mTmpInvalRect中。
接下來,我們就繼續分析ViewRoot類的成員函數invalidateChild的實現。
Step 4. ViewRoot.invalidateChild
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... public void invalidateChild(View child, Rect dirty) { checkThread(); ...... if (mCurScrollY != 0 || mTranslator != null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY != 0) { dirty.offset(0, -mCurScrollY); } if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } } mDirty.union(dirty); if (!mWillDrawSoon) { scheduleTraversals(); } } ...... }
這個函數定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。