簡析View工作的呼叫流程
我們都知道Activity的生命週期流程,我們也知道View繪製的三個方法 onMeasure、onLayout、onDraw
。但是你知道在啟動一個Activity時,它們是工作在哪個生命週期的嗎?這邊我開始做一個完整的分析。以下程式碼都是基於Android 8.0的原始碼進行分析的,由於本人能力有限,不喜勿噴。
首先用一個時序圖來表示這一整個分析流程, performTraversals
是View三大流程 onMeasure、onLayout、onDraw
方法執行開始的地方,我們的分析的就是是從 ActivityThread
到 RootViewImpl
執行 performTraversals
的過程,

image
ActivityThread
這個類是我們平時說的UI執行緒,但是他不是一個真的執行緒類。它是一個App程式執行的管理和執行的類,其中Activty的生命週期就是在這裡通過 Handler
來執行的,我們可以定位到 H
這個內部類,它是一個Handler的子類,在 handleMessage
中包含著四大元件的生命週期的訊息處理。
其中的這段程式碼就是代表Activity啟動的開始, handleLaunchActivity
是其中最重要的方法。
switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break;
定位到 handleLaunchActivity
方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { ... //建立WSM WindowManagerGlobal.initialize(); //在這裡建立了Activity物件,執行onCreate和onStart方法 Activity a = performLaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); reportSizeConfigurations(r); Bundle oldState = r.state; //執行onResume方法,View的工作流程方法 handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason); if (!r.activity.mFinished && r.startsNotResumed) { performPauseActivityIfNeeded(r, reason); if (r.isPreHoneycomb()) { r.state = oldState; } } } else { // If there was an error, for any reason, tell the activity manager to stop us. try { ActivityManager.getService() .finishActivity(r.token, Activity.RESULT_CANCELED, null, Activity.DONT_FINISH_TASK_WITH_ACTIVITY); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } ... }
我們看看 handleResumeActivity
這個方法,這裡首先會根據token來取得要處理的Activity,然後 performResumeActivity
這個方法才是真正執行 onResume
的方法,注意在 onResume
執行的時候,View還沒繪製完成,這時候是拿不到View的寬高的,更不要說在 onCreate
和 onStart
了。decor一開始會被設定為不可見,所以在 onResume
之前,介面對使用者都是不可見的。從 wm.addView(decor, l)
這段程式碼開始才是真正的繪製過程,並且在 r.activity.makeVisible();
這裡開始才把View設定為可見
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); ... // 在這個方法裡面執行onResume的回撥,實際上這時還沒開始執行檢視的測量,所以解釋了為什麼沒辦法在onResume中獲取view的寬高 r = performResumeActivity(token, clearHide, reason); if (r != null) { final Activity a = r.activity; ... if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); //這個view是DecorView,在Activity的attach方法中建立PhoneWindow時建立的。 View decor = r.window.getDecorView(); //decor一開始會被設定為不可見 decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; //把Decorview新增到WindowManager中 wm.addView(decor, l); } else { a.onWindowAttributesChanged(l); } } } } ... if (r.activity.mVisibleFromClient) { //在這裡把DecorView設定為可見,也就是介面可見 r.activity.makeVisible(); } ... }
上面程式碼的 WindowManager
是Activity在 attach()
方法中建立的。ViewManager是一個介面,WindowManager是繼承於ViewManager的一個介面,WindowManager的實現類是WindowManagerImpl,所以上圖程式碼中的addView呼叫的實際是 WindowManagerImpl
裡面的方法
WindowManagerImpl
在WindowManagerImpl中找到了addView方法的實現,但是這邊也很簡單,它把addView的操作委託給了WindowManagerGlobal的addView方法
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }
這幾個類的關係可以用UML圖表示為

image
WindowManagerGlobal
在這個方法中執行新增View的是ViewRootImpl,首先先建立了ViewRootImpl物件,然後把view、建立的ViewRootImpl和佈局引數儲存在三個陣列當中,然後將新增工作託管給了ViewRootImpl
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... //調整佈局引數等 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } //同一個view不能在WindowManager中新增兩次 int index = findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. } ... root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); //儲存佈局引數和view,在更新檢視時用到 mViews.add(view); mRoots.add(root); mParams.add(wparams); // do this last because it fires off messages to start doing things try { //真正完成檢視工作流程的方法 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } }
上面的流程可以大概的總結成
- 父視窗對子視窗的佈局引數進行調整
- 檢查View的新增次數,同一個View不能在WindowManager中新增兩次
- 建立ViewRootImpl,將View、ViewRootImpl、佈局引數儲存在三個陣列中,以供之後的查詢之需
- 呼叫ViewRootImpl.setView()函式,將控制元件交給ViewRootImpl進行託管。
ViewRootImpl
ViewRootImpl
是一個非常重要的類,實現了ViewParent介面,作為整個控制元件樹的根部,負責與WMS進行直接的通訊,負責管理Surface,負責觸發控制元件的測量與佈局,負責觸發控制元件的繪製,同時也是輸入事件的中轉站,我們平時處理的事件分發也是通過這個類的中轉處理之後才來到Activity和各層級的View的。ViewRootImpl如此的重要我們可以看看它的構造方法裡面做了什麼事情
public ViewRootImpl(Context context, Display display) { mContext = context; //從WindowManagerGlobal拿到IWindowSession,它是ViewRootImpl和WMS通訊的代理 mWindowSession = WindowManagerGlobal.getWindowSession(); mDisplay = display; mBasePackageName = context.getBasePackageName(); //把當前執行緒儲存起來,即UI執行緒,在繪製時會對發起的thread和mThread進行比較,不一致時會丟擲異常 mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); mLocation.fillInStackTrace(); mWidth = -1; mHeight = -1; mDirty = new Rect(); mTempRect = new Rect(); mVisRect = new Rect(); mWinFrame = new Rect(); mWindow = new W(this); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; mViewVisibility = View.GONE; mTransparentRegion = new Region(); mPreviousTransparentRegion = new Region(); mFirst = true; // true for the first time the view is added mAdded = false; //建立AttachInfo物件,裡面儲存了Handler,window等 mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); mAccessibilityManager = AccessibilityManager.getInstance(context); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityInteractionConnectionManager, mHandler); mHighContrastTextManager = new HighContrastTextManager(); mAccessibilityManager.addHighTextContrastStateChangeListener( mHighContrastTextManager, mHandler); mViewConfiguration = ViewConfiguration.get(context); mDensity = context.getResources().getDisplayMetrics().densityDpi; mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = new PhoneFallbackEventHandler(context); //Choreographer是一個依附於當前執行緒的訊號同步類,用於通過VSYNC特性進行介面繪製和重新整理,介面的三大流程就是著他的回撥事件裡面進行的 mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); if (!sCompatibilityDone) { sAlwaysAssignFocus = true; sCompatibilityDone = true; } loadSystemProperties(); }
我們定位到 setView
方法,其中重新整理的方法是 requestLayout
,它是 ViewParent
介面的方法,用於重新整理整個檢視樹,當檢視有變動時都會通過這個方法來通知跟佈局重新整理
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... requestLayout(); ... }
繼續看 requestLayout
方法,這裡先進行UI執行緒檢查,然後開始計劃檢視樹的遍歷工作
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { //ui執行緒檢查, checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
mThread
就是 ViewRootImpl
初始化時儲存起來的當前執行緒,它檢查的並不是當前執行緒是否是UI執行緒,而是當前執行緒是否是操作執行緒。這個操作執行緒就是建立 ViewRootImpl
物件的執行緒,其實這裡可以看出來操作不是一定子執行緒不能操作UI,只要建立和執行在同一個執行緒就是可以的
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
可以試試這段程式碼在activity執行會不會報錯
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); MyDialog dialog = new MyDialog(MainActivity.this); dialog.show(); Looper.loop(); } }).start();
然後看 scheduleTraversals
方法,它通過 Choreographer
的回撥來通知執行的時機。為什麼要這樣做呢?
首先來理解一下,圖形介面的繪製,大概是有CPU準備資料,然後通過驅動層把資料交給GPU來進行繪製。圖形API不允許CPU和GPU直接通訊,所以就有了圖形驅動(Graphics Driver)來進行聯絡。Graphics Driver維護了一個序列(Display List),CPU不斷把需要顯示的資料放進去,GPU不斷取出來進行顯示。

image
其中 Choreographer
起排程的作用。統一繪製圖像到 Vsync
的某個時間點。這個就是 VSYNC
(垂直同步)的作用,我們都知道介面重新整理速度每秒60幀以上時就不會感受到介面的卡頓,我們就可以理解為當VSYNC訊號間隔是16毫秒時,我們就不會覺得卡頓了。
最後執行定位到了 performTraversals
方法,這個方法就是View裡面 onMeasure、onLayout、onDraw
方法的起點。
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //Choreographer的回撥,裡面執行介面的繪製 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
performTraversals方法非常長,這裡下面是最簡化後的工作流程,按順序呼叫了performMeasure、performLayout、performDraw方法。而這三個方法由直接或者間距的呼叫了onMeasure、onLayout、onDraw方法。
private void performTraversals() { //定義預測量想要的寬高,對寬高進行賦值,這兩個變數將是生成MeasureSpec引數SPEC_SIZE候選 int desiredWindowWidth; int desiredWindowHeight; ... //執行RunQueue的任務,平時我們通過`view.post`傳送的任務就是在這裡被執行的。通過attachInfo裡面的handler將Runnable物件傳送到主執行緒執行 getRunQueue().executeActions(attachInfo.mHandler); ... //1. 執行測量工作 if (mApplyInsetsRequested) { mApplyInsetsRequested = false; mLastOverscanRequested = mAttachInfo.mOverscanRequested; dispatchApplyInsets(host); if (mLayoutRequested) { //執行performMeasure()對檢視樹進行測量 windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); } } ... //2. 執行佈局 performLayout(lp, mWidth, mHeight); ... //3. 執行檢視繪製 performDraw(); ... }
measureHierarchy的方法執行如下
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { int childWidthMeasureSpec; int childHeightMeasureSpec; boolean windowSizeMayChange = false; if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag, "Measuring " + host + " in display " + desiredWindowWidth + "x" + desiredWindowHeight + "..."); boolean goodMeasure = false; //對於父控制元件是WRAP_CONTENT時,這裡指的是浮動視窗,要進行測量引數的協商 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { // On large screens, we don't want to allow dialogs to just // stretch to fill the entire width of the screen to display // one line of text.First try doing the layout at a smaller // size to see if it will fit. final DisplayMetrics packageMetrics = res.getDisplayMetrics(); res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true); int baseSize = 0; if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { baseSize = (int)mTmpValue.getDimension(packageMetrics); } if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize + ", desiredWindowWidth=" + desiredWindowWidth); if (baseSize != 0 && desiredWindowWidth > baseSize) { childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); //第一次測量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec) + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec)); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { goodMeasure = true; } else { // Didn't fit in that size... try expanding a bit. baseSize = (baseSize+desiredWindowWidth)/2; if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize=" + baseSize); childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); //第二次測量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { if (DEBUG_DIALOG) Log.v(mTag, "Good!"); goodMeasure = true; } } } } if (!goodMeasure) { childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); //第三次測量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //view的測量尺寸和視窗尺寸不一致時告訴外面,視窗尺寸有可能變化 if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { windowSizeMayChange = true; } } if (DBG) { System.out.println("======================================"); System.out.println("performTraversals -- after measure"); host.debug(); } //視窗尺寸是否可能需要發生變化 return windowSizeMayChange; }
上面的程式碼中首先會判斷測量的View的寬度是否為WRAP_CONTENT,這種一般是懸浮視窗,如果是則可能會比普通的視窗多兩次 performMeasure
,上層的View通過MeasureSpec指導子View的測量,我們平時在 onMeasure(int widthMeasureSpec,int heightMeasureSpec)
就是從這邊開始往下傳遞的。它和子控制元件自身期望的尺寸工具決定了子控制元件最終的測量結果。我們具體看看getRootMeasureSpec計算結果
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 = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
上面的方法決定了DecorView測量引數,可以得知
- ViewGroup.LayoutParams.MATCH_PARENT,表示測量引數的大小就是視窗尺寸的大小,測量模式為MeasureSpec.EXACTLY。
- ViewGroup.LayoutParams.WRAP_CONTENT,表示子控制元件可以是它所期望的尺寸,但是不得大於視窗尺寸。
- 預設是固定大小,表示子控制元件的尺寸就是它設定的尺寸大小
我們再往下分析呼叫流程
此時測量工作已經來到了View層級了。performMeasure將measureHierarchy給予的widthSpec與heightSpec交給DecorView。而DecorView就是佈局的頂級View,它是一個ViewGroup,實現了FrameLayout佈局。從這裡開始就會遍歷檢視樹中的所有View的 onMeasure
方法。至此來到了我們熟悉的onMeasure流程
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); } }
measure方法中沒有任何進行測量的程式碼,只是呼叫了 onMeasure
方法,這裡面還做對 onMeasur
的正確使用做了檢查,當沒有 setMeasuredDimension
時會丟擲異常。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... 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 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; } // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer //當開發者沒有呼叫setMeasuredDimension時會丟擲異常 if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("View with id " + getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } ... }
performLayout將會決定所有View四個頂點的位置, host.layout
將會執行DecorView的佈局流程,getMeasuredWidth和getMeasuredHeight是上一步measure得到的結果
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) { final View host = mView; ... try { //decorView的佈局 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); } ... }
host.layout
將呼叫View裡面的layout方法
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; //將l, t, r, b設定給mLeft, mTop, mBottom, mRight boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //從DecorView開始呼叫控制元件樹的onLayout方法 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); } }
從上面可知layout做了如下三件事
View.addOnLayoutChangeListener()
經過測量和佈局之後,每個View已經知道自己都大小和位置了,最後我們來看看最終的繪製方法performDraw。
private void performDraw() { ... try { //呼叫draw方法進行實際的繪製 draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ... }
performDraw
方法很簡單只是呼叫draw方法進行實際的繪製,我們繼續看看
private void draw(boolean fullRedrawNeeded) { ... if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { //當滿足以下條件時將進行硬體繪製 if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { ... //硬體加速繪製 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { ... //軟體繪製 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } }
draw方法中會產生軟體繪製和硬體加速繪製兩個分支,我們看 drawSoftware
方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { final Canvas canvas; try { ... //建立canvas canvas = mSurface.lockCanvas(dirty); if (left != dirty.left || top != dirty.top || right != dirty.right || bottom != dirty.bottom) { attachInfo.mIgnoreDirtyState = true; } // TODO: Do this in native canvas.setDensity(mDensity); } ... try { //進行canvas進行平移 canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; //呼叫DecorView的draw方法,對控制元件樹進行遍歷繪製 mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { if (!attachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call attachInfo.mIgnoreDirtyState = false; } } ... try { //最後將繪製的內容顯示出來 surface.unlockCanvasAndPost(canvas); } catch (IllegalArgumentException e) { Log.e(mTag, "Could not unlock surface", e); mLayoutRequested = true;// ask wm for a new surface next time. //noinspection ReturnInsideFinallyBlock return false; } }
drawSoftware方法主要進行了如下四步操作
- 建立canvas
- 進行canvas進行平移
- 呼叫DecorView的draw方法,對控制元件樹進行遍歷繪製
- 將繪製的內容顯示出來
到這裡View工作流程的onDraw就執行完了,回到handleResumeActivity的程式碼中,通過呼叫 r.activity.makeVisible();
把Activity的內容顯示出來
Activity
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }
從上面的呼叫過程我們把View的繪製和Activity的生命週期聯絡起來了,對於理解整個系統的運作有更深的理解,知道對於UI執行緒得知是一個相對的概念,vsync垂直同步的機制等。View的工作流程是一個非常複雜的過程,裡面的每一個點都值得深入的分析,這裡只是理清了三大流程的呼叫過程。
- Activit啟動時View的繪製過程是從生命週期的onResume開始的
- Activity從onResume開始才是對使用者可見的
- 在onResume或者onCreate中沒辦法直接獲得View的寬高,因為這時測量還沒完成