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();}}......
}
- 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 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();......
}
- 移除 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. 分配 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 源码 图形系统之请求布局相关推荐
- Android 源码 图形系统之 relayoutWindow
在 <Android 源码 图形系统之请求布局> 一节,分析到 ViewRootImpl 类 performTraversals() 方法内调用 relayoutWindow(-) 方法重 ...
- 【Android源码】Activity如何加载布局
我们都知道在Activity中通过setContentView(layoutId)来加载布局文件,使我们的布局文件能够显示在手机上,那么系统是如何将我们的布局文件加载到界面上的呢? setConten ...
- Android源码设计模式探索与实战【建造者模式】
IT行业,一直讲一句话,拼到最后都拼的是"内功",而内功往往就是指我们处理问题的思路.经验.想法,而对于开发者来说,甚至对于产品也一样,都离不开一个"宝典",就 ...
- android源码分析
01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 ...
- 2014年最新720多套Android源码2.0GB免费一次性打包下载
之前发过一个帖子,但是那个帖子有点问题我就重新发一个吧,下面的源码是我从今年3月份开始不断整理源码区和其他网站上的android源码,目前总共有720套左右,根据实现的功能被我分成了100多个类,总共 ...
- Android源码的Binder权限是如何控制,附超全教程文档
从初中级到高级,移动端程序员的进阶宝典 想要成为一名优秀的Android开发,你需要一份完备的 知识体系,在这里,让我们一起成长为自己所想的那样. 下面我们就以 Android 开发为例,从硬技能和软 ...
- 阅读 ANDROID 源码的一些姿势
日常开发中怎么阅读源码 找到正确的源码 IDE是日常经常用的东西,Eclipse就不说了,直接从Android Studio(基于IntelliJ Community版本改造)开始. 我们平时的And ...
- 阅读 Android源码的一些姿势
日常开发中怎么阅读源码 找到正确的源码 IDE 是日常经常用的东西,Eclipse 就不说了,直接从 Android Studio(基于 IntelliJ Community 版本改造)开始. 我们平 ...
- 阅读Android源码的一些姿势
2019独角兽企业重金招聘Python工程师标准>>> 前面吐槽了 有没有必要阅读Android源码,后面觉得只吐槽不太好,还是应该多少弄点干货.需要说明的是,Android每个系统 ...
最新文章
- linux内核中启动页面,Linux内核启动过程分析
- [zz]mysql 和 mongo db 语法对比
- 金叉成功率_技巧!三分钟教会你识别macd真假金叉,让你精准把握买卖点!
- eclipse没有日志_强化公共DHT以抵抗eclipse攻击,ipfs官方还说了什么?
- 长期演进技术(LTE,Long Term Evolution)
- HDU1287+枚举
- js时间戳转化成日期格式
- 美赛如何选题matlab,2017美赛D题—学习记录
- 电商业务中的五大机器学习问题!
- 【Python怎么批量修改文件名称】
- 【计算大于这个整数的最小质数】
- Python3快速入门—7.枚举
- 站在巨人的肩膀上还是站在巨人的脚底下
- android 多张图片渐变切换控件
- Django之DRF自定义action
- 2017年寒假集训分组测试赛2 Ranklist
- opencv中批量读取图片并保存
- Teager能量算子(TEO)_Python实现
- 阿里云和腾讯云这两家对比哪个比较好一些?
- 片段中onCreate(),onCreateView()和onActivityCreated()的区别和用法
热门文章
- Java8新特性之函数式接口,呦呦呦
- CTF杂项之总结(一)
- 禁止文件夹 icloud_如何更改Windows iCloud照片文件夹位置
- 强制删除五笔字型输入法
- lbe+android6.0+免root,lbe安全大师免root版下载
- 微服务之API网关:Kong:概要与安装
- 怎么进行ai抠图?快来了解下这个好方法
- 《Journal of Solar Energy Engineering》期刊介绍(SCI 4区)
- 隐藏文件夹无法查看(隐藏属性灰显无法查看)
- Call to undefined function curl_init()错误解决方法