在《Android 源碼 圖形系統之窗口添加》一節中遺留了 ViewRootImpl 類 setView 方法中調用 requestLayout() 函數分析。現在繼續分析其流程。分析之前先來觀摩一下整體流程。
requestLayout() 方法主要調用了 scheduleTraversals() 進一步處理。
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
boolean mHandlingLayoutInLayoutRequest = false;
......
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
......
}
- post Traversal 同步屏障
- 發佈 CALLBACK_TRAVERSAL 類型回調以在下一幀上運行
- 消耗批量輸入
- 通知 Renderer 幀處理
- 需要時獲取 Draw Lock
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
boolean mTraversalScheduled;
......
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// post Traversal 同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 回調類型:Traversal 回調。處理佈局和繪製。在處理了所有其他異步消息之後運行。
// 發佈回調以在下一幀上運行。回調運行一次,然後會自動刪除。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
// 消耗批量輸入
scheduleConsumeBatchedInput();
}
// 通知 Renderer 幀處理
notifyRendererOfFramePending();
// 需要時獲取 Draw Lock
pokeDrawLockIfNeeded();
}
}
......
}
TraversalRunnable 類 run() 方法僅僅調用了 doTraversal() 方法。
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
......
}
- 移除 Traversal 同步屏障
- 調用 performTraversals() 處理
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除 Traversal 同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
......
performTraversals();
......
}
}
......
}
再研究 performTraversals() 函數之前,我在 performTraversals() 函數內打了很多 Log,這有助於我們理解它的運行流程。
10-15 05:29:08.542 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: ===performTraversals() Begin===
10-15 05:29:08.546 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals desiredWindowWidth=1080 desiredWindowHeight=1776
10-15 05:29:08.546 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals measureHierarchy
10-15 05:29:08.546 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Measuring com.android.internal.policy.PhoneWindow$DecorView{5fe490d V.E...... R.....ID 0,0-0,0} in display 1080x1776...
10-15 05:29:08.546 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: measureHierarchy performMeasure childWidthMeasureSpec=1073742904 childHeightMeasureSpec=1073743600
10-15 05:29:08.546 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performMeasure measure
10-15 05:29:08.555 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals layoutRequested=true
10-15 05:29:08.555 10604-10604/com.tyyj89.abdominalmusclepro I/ViewRootImpl: host=w:1080, h:1776, params=WM.LayoutParams{(0,0)(fillxfill) sim=#110 ty=1 fl=#81810100 wanim=0x103045b vsysui=0x1500 needsMenuKey=2}
10-15 05:29:08.557 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals relayoutWindow
10-15 05:29:08.557 10604-10604/com.tyyj89.abdominalmusclepro D/ViewRootImpl: WindowLayout in layoutWindow:WM.LayoutParams{(0,0)(fillxfill) sim=#110 ty=1 fl=#81810100 wanim=0x103045b vsysui=0x1500 needsMenuKey=2}
10-15 05:29:08.580 10604-10619/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Resizing android.view.ViewRootImpl@9f26740: frame=[0,0][1080,1920] contentInsets=[0,72][0,144] visibleInsets=[0,72][0,144] reportDraw=true
10-15 05:29:08.589 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: relayout: frame=[0,0][1080,1920] overscan=[0,0][0,0] content=[0,72][0,144] visible=[0,72][0,144] visible=[0,72][0,144] outsets=[0,0][0,0] surface=Surface(name=null)/@0xd7a281f
10-15 05:29:08.590 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Visible with new config: {1.0 460mcc2mnc zh_CN ldltr sw360dp w360dp h568dp 480dpi nrml port finger -keyb/v/h -nav/h s.5}
10-15 05:29:08.590 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals updateConfiguration
10-15 05:29:08.590 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Applying new config to window com.tyyj89.abdominalmusclepro/com.tyyj89.abdominalmusclepro.ActionListActivity: {1.0 460mcc2mnc zh_CN ldltr sw360dp w360dp h568dp 480dpi nrml port finger -keyb/v/h -nav/h s.5}
10-15 05:29:08.590 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Visible insets changing to: Rect(0, 72 - 0, 144)
10-15 05:29:08.591 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals Surface allocateBuffers mSurface=Surface(name=null)/@0xd7a281f
10-15 05:29:08.603 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Relayout returned: frame=Rect(0, 0 - 1080, 1920), surface=Surface(name=null)/@0xd7a281f
10-15 05:29:08.603 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals HardwareRenderer setup
10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Ooops, something changed! mWidth=1080 measuredWidth=1080 mHeight=1920 measuredHeight=1776 coveredInsetsChanged=false
10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performMeasure
10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performMeasure measure
10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performLayout
10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Laying out com.android.internal.policy.PhoneWindow$DecorView{5fe490d V.E...... R.....ID 0,0-0,0} to (1080, 1920)
10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performLayout layout
10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Invalidate child: Rect(0, 0 - 1080, 1920)
10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Invalidate child: Rect(0, 0 - 1080, 1776)
10-15 05:29:08.779 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Invalidate child: Rect(0, 1776 - 1080, 1920)
10-15 05:29:08.779 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Invalidate child: Rect(0, 0 - 1080, 72)
10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals dispatchOnGlobalLayout
10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: First: mView.hasFocus()=false
10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Request child focus: focus now android.widget.ListView{52e554b VFED.VC.. .F....ID 0,216-1080,1776 #7f08009e app:id/lv_action_list}
10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: First: requested focused view=android.widget.ListView{52e554b VFED.VC.. .F....ID 0,216-1080,1776 #7f08009e app:id/lv_action_list}
10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals mReportNextDraw=false
10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals scheduleTraversals
10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: ===performTraversals() End===
10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: ===performTraversals() Begin===
10-15 05:29:08.822 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performDraw
10-15 05:29:08.822 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performDraw draw
10-15 05:29:08.822 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: draw fullRedrawNeeded=false
10-15 05:29:08.822 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Draw com.android.internal.policy.PhoneWindow$DecorView{87e9ce8 V.E...... R....... 0,0-1080,1920}/com.tyyj89.abdominalmusclepro/com.tyyj89.abdominalmusclepro.MainActivity: dirty={0,0,1080,1920} surface=Surface(name=null)/@0x8ff2763 surface.isValid()=true, appScale:1.0, width=1080, height=1920
10-15 05:29:08.822 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: draw HardwareRenderer draw mView=com.android.internal.policy.PhoneWindow$DecorView{87e9ce8 V.E...... R....... 0,0-1080,1920} mAttachInfo=android.view.View$AttachInfo@7d681b6
10-15 05:29:08.824 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: ===performTraversals() End===
10-15 05:29:08.826 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: ===performTraversals() Begin===
10-15 05:29:08.862 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals measureHierarchy
10-15 05:29:08.862 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Measuring com.android.internal.policy.PhoneWindow$DecorView{5fe490d V.E...... R.....ID 0,0-1080,1920} in display 1080x1920...
10-15 05:29:08.862 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: measureHierarchy performMeasure childWidthMeasureSpec=1073742904 childHeightMeasureSpec=1073743744
10-15 05:29:08.862 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performMeasure measure
10-15 05:29:08.883 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals layoutRequested=true
10-15 05:29:08.883 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performLayout
10-15 05:29:08.883 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Laying out com.android.internal.policy.PhoneWindow$DecorView{5fe490d V.E...... R.....ID 0,0-1080,1920} to (1080, 1920)
10-15 05:29:08.883 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performLayout layout
10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals dispatchOnGlobalLayout
10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performDraw
10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performDraw draw
10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: draw fullRedrawNeeded=true
10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Draw com.android.internal.policy.PhoneWindow$DecorView{5fe490d V.E...... R.....ID 0,0-1080,1920}/com.tyyj89.abdominalmusclepro/com.tyyj89.abdominalmusclepro.ActionListActivity: dirty={0,0,1080,1920} surface=Surface(name=null)/@0xd7a281f surface.isValid()=true, appScale:1.0, width=1080, height=1920
10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: draw HardwareRenderer draw mView=com.android.internal.policy.PhoneWindow$DecorView{5fe490d V.E...... R.....ID 0,0-1080,1920} mAttachInfo=android.view.View$AttachInfo@3d61fde
10-15 05:29:08.939 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: FINISHED DRAWING: com.tyyj89.abdominalmusclepro/com.tyyj89.abdominalmusclepro.ActionListActivity
10-15 05:29:08.939 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performDraw WindowSession finishDrawing
10-15 05:29:08.942 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: ===performTraversals() End===
下面來總結關鍵的步驟:
- 調用 measureHierarchy(…) 測量圖層,內部會執行測量
- 重新佈局窗口
- Surface 分配 Buffer
- 第二次執行測量
- 執行佈局
- 執行繪製
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
private void performTraversals() {
// 緩存 mView,因爲它在下面經常使用...
final View host = mView;
......
if (host == null || !mAdded)
return;
mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
......
Rect frame = mWinFrame;
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
......
} else {
// DecorView 窗口的寬度和高度就是整個屏幕的寬高
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
......
} else {
......
}
.......
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// 確保執行觸摸模式代碼。
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
......
}
// 1. 測量圖層,內部會執行測量
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
......
if (layoutRequested) {
// 現在清除它,這樣如果在函數的其餘部分中有任何請求佈局,
// 我們將捕獲它並重新運行完整的佈局遍歷。
mLayoutRequested = false;
}
......
int relayoutResult = 0;
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null) {
......
boolean hwInitialized = false;
boolean contentInsetsChanged = false;
boolean hadSurface = mSurface.isValid();
try {
......
// 2. 重新佈局窗口
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
if (mPendingConfiguration.seq != 0) {
// 更新配置
updateConfiguration(mPendingConfiguration, !mFirst);
mPendingConfiguration.seq = 0;
}
......
if (!hadSurface) {
if (mSurface.isValid()) {
// 如果我們要創建一個新的 Surface,
// 那麼我們需要完全重新繪製它。
// 此外,當我們繪製它的時候,我們將推遲並安排一個新的 Traversal。
// 這樣我們就可以在實際繪製之前告訴窗口管理器所有正在顯示的窗口,
// 這樣它就可以一次顯示所有的窗口。
newSurface = true;
mFullRedrawNeeded = true;
mPreviousTransparentRegion.setEmpty();
// 只有在透明區域沒有被請求時才預先初始化,否則就推遲查看整個窗口是否透明
if (mAttachInfo.mHardwareRenderer != null) {
try {
hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
mSurface);
if (hwInitialized && (host.mPrivateFlags
& View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
// 如果透明區域被請求,不要預先分配,因爲它們可能不需要
// 3. 分配 Buffer
mSurface.allocateBuffers();
}
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
}
}
} else if (!mSurface.isValid()) {
......
} else if (surfaceGenerationId != mSurface.getGenerationId() &&
mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
......
}
} catch (RemoteException e) {
}
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
......
final HardwareRenderer hardwareRenderer = mAttachInfo.mHardwareRenderer;
if (hardwareRenderer != null && hardwareRenderer.isEnabled()) {
if (hwInitialized
|| mWidth != hardwareRenderer.getWidth()
|| mHeight != hardwareRenderer.getHeight()) {
hardwareRenderer.setup(mWidth, mHeight, mAttachInfo,
mWindowAttributes.surfaceInsets);
if (!hwInitialized) {
......
}
}
}
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 4. 第二次執行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
layoutRequested = true;
}
}
} else {
......
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 5. 執行佈局
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
// 至此,所有視圖的大小和位置都已確定,我們可以計算出透明面積
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// 重新配置窗口管理器
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
}
// 觸發全局佈局監聽器
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
......
boolean skipDraw = false;
if (mFirst) {
// 處理第一個焦點請求
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
} else {
......
}
}
} else if (mWindowsAnimating) {
if (mRemainingFrameCount <= 0) {
skipDraw = true;
}
mRemainingFrameCount--;
}
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;
......
// 記住我們是否必須報告下一次繪製。
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportNextDraw = true;
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
......
// 6. 執行繪製
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// 再次安排 Traversals
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
......
}
}
mIsInTraversal = false;
}
......
}
lp.width 不等於 ViewGroup.LayoutParams.WRAP_CONTENT,因此會執行 !goodMeasure 分支,這會進一步執行 performMeasure(…) 函數。
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
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;
boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
......
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}
......
}
performMeasure(…) 函數非常簡單,僅僅調用了 mView 的 measure(…) 方法。調用 measure(…) 函數是爲了確定視圖應該有多大。父元素在寬度和高度參數中提供約束信息。視圖的實際測量工作在 onMeasure(int, int) 中執行,由此方法調用。因此,只有 onMeasure(int, int) 可以而且必須被子類覆蓋。這是我們自定義 View 的三部曲的第一部。
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
......
}
重新佈局窗口是調用 relayoutWindow(…) 函數實現的,以後會詳細分析。Surface 分配 Buffer 暫時也不會深入分析。
由於窗口的高度並非充滿屏幕,需要減掉狀態欄高和底部按鍵欄高,因此會第二次執行測量,這是直接調用 performMeasure(…) 進行的。
接着會執行佈局。這會調用 mView layout 函數實現。layout 函數爲視圖及其所有後代指定大小和位置。這是佈局機制的第二階段(首先是測量)。在這個階段中,每個父節點調用其所有子節點的佈局來定位它們。派生類不應重寫此方法。帶有子類的派生類應該重寫 onLayout。在這個方法中,它們應該對每個子節點調用 layout。
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(TAG, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
......
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
......
}
最後會執行繪製。從 Log 中不難發現第一次進入 performTraversals() 函數目標 View 並未繪製,第二次(確切的說是第二次開始處理目標 View 時)才真正開始繪製。這裏調用了 draw(…) 方法。
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
......
if (mReportNextDraw) {
mReportNextDraw = false;
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.fence();
}
if (LOCAL_LOGV) {
Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
}
......
try {
mWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {
}
}
}
......
}
調用 HardwareRenderer 類(硬件渲染器)draw 方法實現 View 繪製,如果未開啓硬件渲染器則調用 drawSoftware(…) 實現“軟繪製”。關於硬件渲染器的詳細分析以後再談。drawSoftware(…) 內部會調用 View 的 draw 方法。
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
......
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
......
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
......
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
......
}
......
}
到此我們就實現了 DecorView 的繪製了,當然如何和底層關聯還需要進一步澄清。