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

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。