View繪製流程(一)

最近在學習 View 的繪製流程,看了幾篇不錯的部落格( ViewRootImpl的獨白,我不是一個View(佈局篇) 、 Android應用層View繪製流程與原始碼分析 )自己對照原始碼,梳理了一遍。
相關類
- Activity: 一個Activity是一個應用程式元件,提供一個螢幕,使用者可以用來互動為了完成某項任務,例如撥號、拍照。
- View: 作為所有圖形的基類。
- ViewGroup: 對View繼承擴充套件為檢視容器類。
- Window: 它概括了Android視窗的基本屬性和基本功能。(抽象類)
- PhoneWindow: Window的子類。
- DecorView: 介面的根View,PhoneWindow的內部類。
- ViewRootImpl: ViewRoot是GUI管理系統與GUI呈現系統之間的橋樑。
- WindowManangerService: 簡稱WMS,它的作用是管理所有應用程式中的視窗,並用於管理使用者與這些視窗發生的的各種互動。
View樹的繪圖流程是在ViewRootImpl類的performTraversals()方法開始的
ViewRootImpl簡介
ViewRootImpl是View中的最高層級,屬於所有View的根(但ViewRootImpl不是View,只是實現了ViewParent介面),實現了View和WindowManager之間的通訊協議。
ViewRootImpl的初始化

WindowManager
繼承 ViewManger
,從 ViewManager
這個類名來看就是用來對View類進行管理的,從 ViewManager
介面中的新增、更新、刪除View的方法也可以看出來 WindowManager
對View的管理。
WindowManagerImpl
為 WindowManager
的實現類。 WindowManagerImpl
內部方法實現都是由代理類 WindowManagerGlobal
完成,而 WindowManagerGlobal
是一個單例,也就是一個程序中只有一個 WindowManagerGlobal
物件服務於所有頁面的View。
public final class WindowManagerGlobal { /*******部分程式碼省略**********/ //所有Window物件中的View private final ArrayList<View> mViews = new ArrayList<View>(); //所有Window物件中的View所對應的ViewRootImpl private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); //所有Window物件中的View所對應的佈局引數 private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); /*******部分程式碼省略**********/ }
WindowManagerGlobal
在其內部儲存著 ViewRootImpl
和 View
例項的對映關係(順序儲存)。
在Activity的onResume之後,當前Activity的Window物件中的View會被新增在WindowManager中。
public final class ActivityThread { /*******部分程式碼省略**********/ final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { /*******部分程式碼省略**********/ ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { /*******部分程式碼省略**********/ if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; //window的型別:一個應用視窗型別(所有的應用視窗型別都展現在最頂部)。 l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; //將decor新增在WindowManager中 wm.addView(decor, l); } /*******部分程式碼省略**********/ } else { try { ActivityManagerNative.getDefault() .finishActivity(token, Activity.RESULT_CANCELED, null, false); } catch (RemoteException ex) { } } } }
wm.addView(decor, l);
方法的具體實現是在 WindowManager
的代理類 WindowManagerGlobal
中
public final class WindowManagerGlobal { /*******部分程式碼省略**********/ public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { /*******部分程式碼省略**********/ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; //宣告ViwRootImpl ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. if (mSystemPropertyUpdater == null) { mSystemPropertyUpdater = new Runnable() { @Override public void run() { synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { mRoots.get(i).loadSystemProperties(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); } /*******部分程式碼省略**********/ //建立ViwRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); //將Window所對應的View、ViewRootImpl、LayoutParams順序新增在WindowManager中 mViews.add(view); mRoots.add(root); mParams.add(wparams); } try { //把將Window所對應的View設定給建立的ViewRootImpl //通過ViewRootImpl來更新介面並完成Window的新增過程。 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { /*******部分程式碼省略**********/ } } }
建立 ViewRootImpl
例項後,將將 Window
所對應的 View
、 ViewRootImpl
、 LayoutParams
順序新增在 WindowManager
中,然後將 Window
所對應的 View
設定給建立的 ViewRootImpl
: root.setView(view, wparams, panelParentView);
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { /*******部分程式碼省略**********/ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { //ViewRootImpl成員變數view進行復制,以後操作的都是mView。 mView = view; /*******部分程式碼省略**********/ //Window在新增完之前先進行一次佈局,確保以後能再接受系統其它事件之後重新佈局。 //對View完成非同步重新整理,執行View的繪製方法。 requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); //將該Window新增到螢幕。 //mWindowSession實現了IWindowSession介面,它是Session的客戶端Binder物件. //addToDisplay是一次AIDL的跨程序通訊,通知WindowManagerService新增IWindow res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel); } catch (RemoteException e) { /*******部分程式碼省略**********/ } finally { if (restore) { attrs.restore(); } } /*******部分程式碼省略**********/ //設定當前View的mParent view.assignParent(this); /*******部分程式碼省略**********/ } } } }
requestLayout();
方法請求view繪製,其過程主要是在 ViewRootImpl
的 performTraversals
方法中。
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { /*******部分程式碼省略**********/ //請求對介面進行佈局 @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } /*******部分程式碼省略**********/ //安排任務 void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } //做任務 void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals"); try { //執行任務 performTraversals(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } } }
整個View樹的繪圖流程是在 ViewRootImpl
類的 performTraversals()
方法(這個方法巨長)開始的,該方法做的執行過程主要是根據之前設定的狀態,判斷是否重新計算檢視大小 (measure)
、是否重新放置檢視的位置 (layout)
、以及是否重繪 (draw)
,其核心也就是通過判斷來選擇順序執行這三個方法。
private void performTraversals() { ...... //最外層的根檢視的widthMeasureSpec和heightMeasureSpec由來 //lp.width和lp.height在建立ViewGroup例項時等於MATCH_PARENT int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ...... performLayout(lp, desiredWindowWidth, desiredWindowHeight); ...... performDraw(); ...... }
這裡的最外層根檢視是 DecorView
,也就是 mView
,在 WindowManagerGlobal
中的 addview
中傳遞過來的。
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY); break; ...... } return measureSpec; }
該方法的是用來測Root View的。上面傳入引數後這個函式走的是 MATCH_PARENT
,使用 MeasureSpec.makeMeasureSpec
方法組裝一個 MeasureSpec
, MeasureSpec
的 specMode
等於 EXACTLY , specSize
等於 windowSize ,也就是為何根檢視總是全屏的原因。
View的測量
ViewRootImpl
呼叫 performMeasure
執行Window對應的View的測量。
performMeasure measure onMeasure measure

private fun performMeasure(childWidthMeasureSpec: Int, childHeightMeasureSpec: Int) { if (mView == null) { return } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure") try { //mView在Activity中為DecorView(FrameLayout) mView.measure(childWidthMeasureSpec, childHeightMeasureSpec) } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW) } }
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ...... //final方法,子類不可重寫 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ...... //回撥onMeasure()方法 onMeasure(widthMeasureSpec, heightMeasureSpec); ...... } }
為整個View樹計算實際的大小,然後設定實際的高和寬,每個View控制元件的實際寬高都是由父檢視和自身決定的。實際的測量是在 onMeasure
方法進行,所以在View的子類需要重寫 onMeasure
方法,這是因為 measure
方法是 final 的,不允許過載,所以View子類只能通過過載 onMeasure
來實現自己的測量邏輯。
int widthMeasureSpec
:他由兩部分組成, 高2位表示MODE ,定義在MeasureSpec類(View的內部類)中,有三種類型, MeasureSpec.EXACTLY 表示確定大小, MeasureSpec.AT_MOST 表示最大大小, MeasureSpec.UNSPECIFIED 不確定。 低30位表示size ,也就是父View的大小。對於系統Window類的DecorVIew物件Mode一般都為MeasureSpec.EXACTLY ,而size分別對應螢幕寬高。對於子View來說大小是由父View和子View共同決定的。
//View的onMeasure預設實現方法 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
onMeasure
預設的實現僅僅呼叫了 setMeasuredDimension
,它對View的成員變數 mMeasuredWidth
和 mMeasuredHeight
變數賦值, measure
的主要目的就是對View樹中的每個View的 mMeasuredWidth
和 mMeasuredHeight
進行賦值,所以一旦這兩個變數被賦值意味著該View的測量工作結束。
預設的尺寸大小即傳入的引數都是通過 getDefaultSize
返回的,我們就看一下該方法的實現。
public static int getDefaultSize(int size, int measureSpec) { int result = size; //通過MeasureSpec解析獲取mode與size int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
specMode
等於 AT_MOST 或 EXACTLY 就返回 specSize
。
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }
建議的最小寬度和高度都是由View的Background尺寸與通過設定View的 miniXXX
屬性共同決定的。也只有當Mode為 MeasureSpec.UNSPECIFIED 時才會使用該尺寸。
到此一次最基礎的元素View的 measure
過程就完成了。
View實際是巢狀的,而且measure是遞迴傳遞的,所以每個View都需要 measure
,能夠巢狀的View都是ViewGroup的子類,所以在ViewGroup中定義了 measureChildren
, measureChild
, measureChildWithMargins
方法來對子檢視進行測量, measureChildren
內部實質只是迴圈呼叫 measureChild
, measureChild
和 measureChildWithMargins
的區別就是是否把 margin 和 padding 也作為子檢視的大小。 ViewGroup 本身不呼叫 measureChildWithMargins
和 measureChildren
方法,由繼承類通過for迴圈呼叫此方法進行子View的測量。下面看一下ViewGroup中稍微複雜的 measureChildWithMargins
方法。
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { //獲取子檢視的LayoutParams final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //調整MeasureSpec //通過這兩個引數以及本身的LayoutParams來共同決定子檢視的測量規則 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); //調運子View的measure方法,子View的measure中會回撥子View的onMeasure方法 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
該方法就是對父檢視提供的 measureSpec
引數結合子檢視的 LayoutParams 引數進行了調整,然後再來呼叫 child.measure()
方法,具體通過方法 getChildMeasureSpec
來進行引數調整。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //獲取當前Parent View的Mode和Size int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //獲取Parent size與padding差值(也就是Parent剩餘大小),若差值小於0直接返回0 int size = Math.max(0, specSize - padding); //定義返回值儲存變數 int resultSize = 0; int resultMode = 0; //依據當前Parent的Mode進行switch分支邏輯 switch (specMode) { // Parent has imposed an exact size on us //預設Root View的Mode就是EXACTLY case MeasureSpec.EXACTLY: if (childDimension >= 0) { //如果child的layout_wOrh屬性在xml或者java中給予具體大於等於0的數值 //設定child的size為真實layout_wOrh屬性值,mode為EXACTLY resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果child的layout_wOrh屬性在xml或者java中給予MATCH_PARENT // Child wants to be our size. So be it. //設定child的size為size,mode為EXACTLY resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果child的layout_wOrh屬性在xml或者java中給予WRAP_CONTENT //設定child的size為size,mode為AT_MOST // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; ...... //其他Mode分支類似 } //將mode與size通過MeasureSpec方法整合為32位整數返回 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }

getChildMeasureSpec的規則
getChildMeasureSpec
的邏輯是通過其父View提供的 MeasureSpec
引數得到 specMode
和 specSize
,然後根據計算出來的 specMode
以及子View的 childDimension
(layout_width或layout_height)來計算自身的 measureSpec
,如果其本身包含子檢視,則計算出來的 measureSpec
將作為呼叫其子檢視 measure
函式的引數,同時也作為自身呼叫 setMeasuredDimension
的引數,如果其不包含子檢視則預設情況下最終會呼叫 onMeasure
的預設實現,並最終呼叫到 setMeasuredDimension
。
最終決定View的 measure
大小是View的 setMeasuredDimension
方法,所以我們可以通過 setMeasuredDimension
設定死值來設定View的 mMeasuredWidth 和 mMeasuredHeight 的大小,但是一個好的自定義View應該會根據子檢視的 measureSpec
來設定 mMeasuredWidth 和 mMeasuredHeight 的大小,這樣的靈活性更大
View測量總結
在 Activity 的 onResume
之後,當前 Activity 的 Window 物件中的View(DecorView)會被新增在 WindowManager 中。也就是在 ActivityThread 的 handleResumeActivity
方法中呼叫 wm.addView(decor, l);
將DecorView新增到 WindowManager 中;
WindowManager繼承 ViewManager ,它的實現類為 WindowManagerImpl ,該類中的方法的具體實現是由其代理類 WindowManagerGlobal 實現的;
在它的 addView
方法中會建立 ViewRootImpl 的例項,然後將Window對應的View(DecorView),ViewRootImpl,LayoutParams順序新增在WindowManager中,最後將Window所對應的View設定給建立的ViewRootImpl,通過 ViewRootImpl 來更新介面並完成Window的新增過程;
設定view呼叫的是 ViewRootImpl 的 setView
方法,在該方法中呼叫 requestLayout();
方法來非同步執行view的繪製方法;之後將 Window 新增到螢幕,通過 WMS (跨程序通訊)
在 requestLayout
方法中最終會呼叫 ViewRootImpl 的 performTraversals();
方法,該方法做的執行過程主要是根據之前設定的狀態,判斷是否重新計算檢視大小 (measure)
、是否重新放置檢視的位置 (layout)
、以及是否重繪 (draw)
,其核心也就是通過判斷來選擇順序執行這三個方法: performMeasure
、 performLayout
、 performDraw
;
在 performMeasure
方法中呼叫的是 View 的 measure
方法,該方法是 final 修飾,不能被子類重寫,在該方法中實際呼叫的是 View 的 onMeasure
方法,子類可以重寫 onMeasure
方法來實現自己的測量規則。
View預設的 onMeasure
方法很簡單只是呼叫了 setMeasuredDimension
方法,該方法的作用是給 View 的成員變數 mMeasuredWidth 和 mMeasuredHeight 賦值,View的測量主要就是給這兩個變數賦值,這兩個變數一旦賦值,也就意味著測量過程的結束。
setMeasuredDimension
方法傳入的尺寸是通過 getDefaultSize(int size, int measureSpec);
方法返回的,在
getDefaultSize
方法中解析 measureSpec 的 Mode 和 Size ,如果Mode為 MeasureSpec.AT_MOST 或者 MeasureSpec.EXACTLY ,最終的size的值為解析後的size;如果 Mode 為 MeasureSpec.UNSPECIFIED ,最終的size為建議的最小值= getSuggestedMinimumWidth
,該方法的具體實現為 return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
,建議的最小寬度和高度都是由View的Background尺寸與通過設定View的 miniXXX
屬性共同決定的
measureSpec是由 getRootMeasureSpec
方法決定的: measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
根佈局的大小是 Window 的大小,Window大小是不能改變的,總是全屏的。
View實際是巢狀的,而且measure是遞迴傳遞的,所以每個View都需要measure,能夠巢狀的View都是ViewGroup的子類,所以在ViewGroup中定義了 measureChildren
, measureChild
, measureChildWithMargins
方法來對子檢視進行測量, measureChildren
內部實質只是迴圈呼叫 measureChild
, measureChild
和 measureChildWithMargins
的區別就是是否把 margin 和 padding 也作為子檢視的大小。
measureChildWithMargins
方法的作用就是對 父View 提供的 measureSpec 引數結合 子View 的 LayoutParams 引數進行了調整,然後再來呼叫 child.measure()
方法,具體通過方法 getChildMeasureSpec
方法來進行引數調整。計算出來自身的 measureSpec 作為呼叫其子檢視 measure
方法的引數,同時也作為自身呼叫 setMeasuredDimension
的引數,如果其不包含子檢視則預設情況下最終會呼叫 onMeasure
的預設實現,並最終呼叫到 setMeasuredDimension
。
最終決定 View 的 measure 大小是 View 的 setMeasuredDimension
方法,該方法就是設定mMeasuredWidth和mMeasuredHeight的大小,ViewGroup在 onMeasure
方法呼叫 setMeasuredDimension
之前調整了 measureSpec 。
