承接前文《源碼解析:Dialog、PopupWindow 與 Activity 第一個 View 的由來》,本文深入梳理 View 繪製流程中四大核心回調——onAttachedToWindow、onMeasure、onLayout、onDraw——在 ViewRootImpl 中的完整調用鏈與源碼邏輯。
當 Activity 的 DecorView(實為 FrameLayout)被添加到 WindowManager 後,ViewRootImpl.setView() 被調用,內部調用 requestLayout() → scheduleTraversals() → 發送 DO_TRAVERSAL 消息,最終在 handleMessage() 中觸發 performTraversals(),這是整個繪製流程的核心入口。
首次執行時 (mFirst == true),調用 host.dispatchAttachedToWindow(attachInfo, 0),依次觸發:
onAttachedToWindow() —— 僅保證在 onDraw 前調用,不保證在 onMeasure 前後。OnAttachStateChangeListener.onViewAttachedToWindow()onWindowVisibilityChanged()隨後調用 fitSystemWindows() 處理系統窗口邊距(狀態欄、導航欄),並可能再次觸發 requestLayout()。
核心邏輯在 performTraversals() 中段,根據 LayoutParams(WRAP_CONTENT、MATCH_PARENT、具體尺寸)計算 MeasureSpec,調用 host.measure()。
View.measure() 內部檢查標志位,最終回調 onMeasure(),並設置 LAYOUT_REQUIRED 標志。
若 mLayoutRequested 為真,調用 host.layout(0, 0, measuredWidth, measuredHeight)。
View.layout() → setFrame() 判斷位置是否改變 → 若改變或需佈局,回調 onLayout() 並觸發 OnLayoutChangeListener。
setFrame() 內部會調用 invalidate() 標記髒區域,最終導致重繪。
調用 draw(fullRedrawNeeded) → View.draw(canvas),繪製順序:
mBGDrawable)onDraw(canvas))dispatchDraw(canvas))最後通過 WindowSession.finishDrawing() 通知底層繪製完成。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
mWindowAttachCount++;
onAttachedToWindow();
// 分發 listener 回調
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
int vis = info.mWindowVisibility;
if (vis != GONE) onWindowVisibilityChanged(vis);
}
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT
|| widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec) {
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
onMeasure(widthMeasureSpec, heightMeasureSpec);
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set measured dimension");
}
mPrivateFlags |= LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
public 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;
}
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = (mLeft != left || mRight != right || mTop != top || mBottom != bottom);
if (changed) {
invalidate(sizeChanged);
// ... 更新 mLeft/mTop/mRight/mBottom 等
}
return changed;
}
public void draw(Canvas canvas) {
// 1. 背景
if (!dirtyOpaque) background.draw(canvas);
// 2. 內容
if (!dirtyOpaque) onDraw(canvas);
// 3. 子 View
dispatchDraw(canvas);
// 4. 裝飾
onDrawScrollBars(canvas);
}
setView()
→ requestLayout()
→ scheduleTraversals()
→ performTraversals()
├─ dispatchAttachedToWindow() → onAttachedToWindow()
├─ fitSystemWindows() → requestLayout()
├─ measure() → onMeasure()
├─ layout() → setFrame() → onLayout()
└─ draw() → onDraw() → dispatchDraw()
relayoutWindow() 與 computeInternalInsets() 的具體作用待深入分析。WindowSession.finishDrawing() 底層交互機制。