Android 源码 图形系统之请求布局

2024-05-01 21:08

本文主要是介绍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;......@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}}......
}
  1. post Traversal 同步屏障
  2. 发布 CALLBACK_TRAVERSAL 类型回调以在下一帧上运行
  3. 消耗批量输入
  4. 通知 Renderer 帧处理
  5. 需要时获取 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 LockpokeDrawLockIfNeeded();}}    ......
}

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 {@Overridepublic void run() {doTraversal();}}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();......
}
  1. 移除 Traversal 同步屏障
  2. 调用 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===

下面来总结关键的步骤:

  1. 调用 measureHierarchy(…) 测量图层,内部会执行测量
  2. 重新布局窗口
  3. Surface 分配 Buffer
  4. 第二次执行测量
  5. 执行布局
  6. 执行绘制

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. 分配 BuffermSurface.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) {// 再次安排 TraversalsscheduleTraversals();} 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 源码 图形系统之请求布局的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/952673

相关文章

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

SpringBoot请求参数传递与接收示例详解

《SpringBoot请求参数传递与接收示例详解》本文给大家介绍SpringBoot请求参数传递与接收示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录I. 基础参数传递i.查询参数(Query Parameters)ii.路径参数(Path Va

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

使用Python的requests库来发送HTTP请求的操作指南

《使用Python的requests库来发送HTTP请求的操作指南》使用Python的requests库发送HTTP请求是非常简单和直观的,requests库提供了丰富的API,可以发送各种类型的HT... 目录前言1. 安装 requests 库2. 发送 GET 请求3. 发送 POST 请求4. 发送

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

python运用requests模拟浏览器发送请求过程

《python运用requests模拟浏览器发送请求过程》模拟浏览器请求可选用requests处理静态内容,selenium应对动态页面,playwright支持高级自动化,设置代理和超时参数,根据需... 目录使用requests库模拟浏览器请求使用selenium自动化浏览器操作使用playwright

SpringBoot监控API请求耗时的6中解决解决方案

《SpringBoot监控API请求耗时的6中解决解决方案》本文介绍SpringBoot中记录API请求耗时的6种方案,包括手动埋点、AOP切面、拦截器、Filter、事件监听、Micrometer+... 目录1. 简介2.实战案例2.1 手动记录2.2 自定义AOP记录2.3 拦截器技术2.4 使用Fi

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class