Android(安卓)源码 图形系统之请求布局
在《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=177610-15 05:29:08.546 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals measureHierarchy10-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=107374360010-15 05:29:08.546 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performMeasure measure10-15 05:29:08.555 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals layoutRequested=true10-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 relayoutWindow10-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 [email protected]: frame=[0,0][1080,1920] contentInsets=[0,72][0,144] visibleInsets=[0,72][0,144] reportDraw=true10-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)/@0xd7a281f10-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 updateConfiguration10-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)/@0xd7a281f10-15 05:29:08.603 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Relayout returned: frame=Rect(0, 0 - 1080, 1920), surface=Surface(name=null)/@0xd7a281f10-15 05:29:08.603 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals HardwareRenderer setup10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: Ooops, something changed! mWidth=1080 measuredWidth=1080 mHeight=1920 measuredHeight=1776 coveredInsetsChanged=false10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performMeasure10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performMeasure measure10-15 05:29:08.604 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performLayout10-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 layout10-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 dispatchOnGlobalLayout10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: First: mView.hasFocus()=false10-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=false10-15 05:29:08.806 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals scheduleTraversals10-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 performDraw10-15 05:29:08.822 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performDraw draw10-15 05:29:08.822 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: draw fullRedrawNeeded=false10-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=192010-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} [email protected]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 measureHierarchy10-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=107374374410-15 05:29:08.862 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performMeasure measure10-15 05:29:08.883 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals layoutRequested=true10-15 05:29:08.883 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performLayout10-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 layout10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals dispatchOnGlobalLayout10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performTraversals performDraw10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performDraw draw10-15 05:29:08.901 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: draw fullRedrawNeeded=true10-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=192010-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} [email protected]10-15 05:29:08.939 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: FINISHED DRAWING: com.tyyj89.abdominalmusclepro/com.tyyj89.abdominalmusclepro.ActionListActivity10-15 05:29:08.939 10604-10604/com.tyyj89.abdominalmusclepro V/ViewRootImpl: performDraw WindowSession finishDrawing10-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 的绘制了,当然如何和底层关联还需要进一步澄清。
更多相关文章
- android涂鸦程序(在图像上绘制)
- Android(安卓)DownloadProvider分析
- 111111111
- Android(安卓)FrameWork——Binder机制详解
- Android(安卓)Camera2 Hal3(一)初始化
- Android属性动画与自定义属性动画
- Android(安卓)Monkey测试及源码分析
- 使用FileProvider共享文件
- Android调用摄像头拍照和从相册中选择(上传、更换头像)