工作一段時間了,但是感覺自己對View的三大流程還不是理解透徹。所以主要根據(jù)《Android開發(fā)藝術探索》一書和查看源碼去了解下View的三大流程。
在《Android開發(fā)藝術探索》中說到,ViewRoot是連接WindowManager和DectorView的紐帶,View的三大流程都是通過ViewRoot實現(xiàn)的。而ViewRootImpl是ViewRoot的實現(xiàn)。在Activity被創(chuàng)建之后,會將DectorView添加到window上,同時創(chuàng)建ViewRootImpl,并將兩者關聯(lián)起來(通過ViewRootImpl.setView)。
View的繪制流程是從ViewRoot.performTraversals()開始的,并通過調(diào)用performMeasure、performLayout、performDraw完成頂級View的三大流程。

performTraversals方法很長,分段閱讀畢竟容易理解和消化。首先是很長的一段(哈哈),主要作用就是確定當前窗體大小并進行view樹的測量(測量會提出來下一段分析)
// cache mView since it is used so much below...
final View host = mView;
if (host == null || !mAdded)
return;
//一些窗口變量的處理
Rect frame = mWinFrame;
if (mFirst) {//是否第一次請求,構造方法中為true
....
//---Activity當前的Window的寬高的確定---
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
//視圖大小發(fā)生改變,重繪相關標志位置為true
mFullRedrawNeeded = true;//需要重新繪制標志位
mLayoutRequested = true; //要求重新Layout標志位
windowSizeMayChange = true;//Window的尺寸可能改變
}
}
....
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
...
//--檢測邊襯區(qū)域是否發(fā)生變化,有變化則 insetsChanged 變量置為true
//測量Window的可能大小,實際上進行了measure()測量過程,只不過這個測量過程不屬于三大流程
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
...
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
mLayoutRequested = false;
}
//前面已經(jīng)做了一次measure()工作,host寬高和當前窗口寬高不一致則Activity窗口發(fā)生變化
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the activity was just relaunched, it might have unfrozen the task bounds (while
// relaunching), so we need to force a call into window manager to pick up the latest
// bounds.
windowShouldResize |= mActivityRelaunched;
//檢測相關邊襯區(qū)域,Activity窗口是否指定了額外的內(nèi)容區(qū)域邊襯和可見區(qū)域邊襯
// Determine whether to compute insets.
// If there are no inset listeners remaining then we may still need to compute
// insets in case the old insets were non-empty and must be reset.
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
...
final boolean isViewVisible = viewVisibility == View.VISIBLE;
final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
//1.Activity窗口是第一次執(zhí)行測量、布局和繪制操作,即mFirst == true
//2.前面windowShouldResize==true,即Activity窗口的大小發(fā)生了變化
//3.前面insetsChanged==true,即Activity窗口的內(nèi)容區(qū)域邊襯發(fā)生了變化
//4.viewVisibilityChanged==true,Activity窗口的可見性發(fā)生了變化
//5.變量params指向了一個WindowManager.LayoutParams對象,Activity窗口的屬性發(fā)生了變化
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
mForceNextWindowRelayout = false;
if (isViewVisible) {
// If this window is giving internal insets to the window
// manager, and it is being added or changing its visibility,
// then we want to first give the window manager "fake"
// insets to cause it to effectively ignore the content of
// the window during layout. This avoids it briefly causing
// other windows to resize/move based on the raw frame of the
// window, waiting until we can finish laying out this window
// and get back to the window manager with the ultimately
// computed insets.
//Activity窗口是否指定了額外的內(nèi)容區(qū)域邊襯和可見區(qū)域邊襯
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
}
.....
try {
....
//請求WindowManagerService服務計算Activity窗口的大小以及過掃描區(qū)域邊襯大小和可見區(qū)域邊襯大小
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
.....
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
....
if (contentInsetsChanged) {
mAttachInfo.mContentInsets.set(mPendingContentInsets);
}
....
} catch (RemoteException e) {
}
...
//計算完畢之后,Activity窗口的大小就會保存在成員變量mWinFrame中
//變量frame和mWinFrame引用的是同一個Rect對象
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
// !!FIXME!! This next section handles the case where we did not get the
// window size we asked for. We should avoid this by getting a maximum size from
// the window session beforehand.
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
....
//-------進行測量過程,下面分析-------
} else {
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
//判斷window是否有移動,發(fā)生移動則執(zhí)行移動動畫
maybeHandleWindowMove(frame);
}
上面這一大段代碼,主要作用其實就是為了確定Activity窗口的大小,包括內(nèi)容窗口大小和相關的邊襯區(qū)域大小的確定。
- Activity窗口是第一次執(zhí)行測量、布局和繪制操作,即mFirst == true
- 前面windowShouldResize==true,即Activity窗口的大小發(fā)生了變化
- 前面insetsChanged==true,即Activity窗口的內(nèi)容區(qū)域邊襯發(fā)生了變化
- viewVisibilityChanged==true,Activity窗口的可見性發(fā)生了變化
- 變量params != null,指向了一個WindowManager.LayoutParams對象,Activity窗口的屬性發(fā)生了變化
當上面5中情況中一種情況為true,則activity窗口大小發(fā)生變化需要重新測量。并通過WMS請求計算activity窗口大小。然后開始測量流程。
//mStopped==true,該窗口activity處于停止狀態(tài)
//mReportNextDraw,Window上報下一次繪制
if (!mStopped || mReportNextDraw) {
//觸摸模式發(fā)生了變化,且檢測焦點的控件發(fā)生了變化
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
//1. 焦點控件發(fā)生變化
//2. 窗口寬高測量值 != WMS計算的mWinFrame寬高
//3. contentInsetsChanged==true,邊襯區(qū)域發(fā)生變化
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//------開始執(zhí)行測量操作--------
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
//根據(jù)水平/垂直權重值判斷是否重新測量
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
//----有相關權重,需要重新測量-----
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
- 焦點控件發(fā)生變化
- 窗口寬高測量值 != WMS計算的mWinFrame寬高
- contentInsetsChanged==true,邊襯區(qū)域發(fā)生變化
當上述情況之一出現(xiàn)則進行測量流程。測量完成后根據(jù)是否有配置權重進行再次測量。
//layout布局要求標志位
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
//--------開始執(zhí)行布局操作---------
performLayout(lp, mWidth, mHeight);
// By this point all views have been sized and positioned
// We can compute the transparent area
//計算透明區(qū)域
....
}
.....
mFirst = false;
....
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportNextDraw();
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
....
//-----開始執(zhí)行繪制操作-------
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();//重新執(zhí)行performTraversals
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
Invalidate、postInvalidate、requestLayout應用場景?
哪一個流程可以放在子線程中去執(zhí)行?
參考資料
View繪制流程及源碼解析(一)——performTraversals()源碼分析
Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析
《Android開發(fā)藝術探索》