首页 > 代码库 > 源码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。
源码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。
从前文《 源码解析:dialog, popupwindow, 和activity 的第一个view是怎么来的?》中知道了activity第一个view或者说根view或者说mDecorView 其实就是一个FrameLayout,以及是在系统handleResume的时候加入到系统windowManager中的,并由framework中的ViewRootImpl 接管,通过ViewRootImpl.setView() 开始整个显示过程的。这次着重梳理一下view的显示过程(onAttach, onMeasure, onLayout, onDraw )在源码中的过程。
从ViewRootImpl.setview 开始。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; <span style="color:#ff0000;">requestLayout();</span> if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } try { mOrigWindowType = mWindowAttributes.type; res = sWindowSession.add(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } }在第一次赋值mView的时候,会调用ViewRootImpl.requestLayout();
public void requestLayout() { checkThread(); mLayoutRequested = true; scheduleTraversals(); }进而scheduleTraversals();
public void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //noinspection ConstantConditions if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) { final long now = System.nanoTime(); Log.d(TAG, "Latency: Scheduled traversal, it has been " + ((now - mLastTraversalFinishedTimeNanos) * 0.000001f) + "ms since the last traversal finished."); } sendEmptyMessage(DO_TRAVERSAL); } }进而在handleMessage() 中
@Override public void handleMessage(Message msg) { switch (msg.what) { case DO_TRAVERSAL: performTraversals(); <span style="white-space:pre"> </span>}进而就是performTraversals(),也就是本次分析的重点。
这个函数比较长,不适合把全部函数代码都 贴上来。就分段叙述。
1. 函数刚开始的部分,初始化了一些后面会用到的变量和标志位。
final View host = mView; WindowManager.LayoutParams lp = mWindowAttributes; final View.AttachInfo attachInfo = mAttachInfo; CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get(); Rect frame = mWinFrame; mTraversalScheduled = false; mWillDrawSoon = true; boolean windowSizeMayChange = false; boolean fullRedrawNeeded = mFullRedrawNeeded; boolean newSurface = false; boolean surfaceChanged = false;
2. 接下来,是一个重要的判断, 如果是初次执行,则调用host.dispatchAttachedToWindow(attachInfo, 0);
if (mFirst) { // 略去一大堆赋值 mLastConfiguration.setTo(host.getResources().getConfiguration()); host.dispatchAttachedToWindow(attachInfo, 0); //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); host.fitSystemWindows(mAttachInfo.mContentInsets); } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { if (DEBUG_ORIENTATION) Log.v(TAG, "View " + host + " resized to: " + frame); fullRedrawNeeded = true; mLayoutRequested = true; windowSizeMayChange = true; } }在dispatchAttachedToWindow()中重点处理了三件事:
2.1. onAttachedToWindow();
2.2. listener.onViewAttachedToWindow(this);
2.3 onWindowVisibilityChanged(vis);
void dispatchAttachedToWindow(AttachInfo info, int visibility) { //System.out.println("Attached! " + this); mAttachInfo = info; mWindowAttachCount++; onAttachedToWindow(); final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = mOnAttachStateChangeListeners; if (listeners != null && listeners.size() > 0) { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that // could mutate the list by calling the various add/remove methods. This prevents // the array from being modified while we iterate it. for (OnAttachStateChangeListener listener : listeners) { listener.onViewAttachedToWindow(this); } } int vis = info.mWindowVisibility; if (vis != GONE) { onWindowVisibilityChanged(vis); } }在这里onAttachedToWindow() 的注释带来了一个问题:仅保证会在onDraw 前调用,而不保证在onMeasure 之前或者之后调用 onAttachedToWindow。
/** * This is called when the view is attached to a window. At this point it * has a Surface and will start drawing. Note that this function is * guaranteed to be called before {@link #onDraw(android.graphics.Canvas)}, * however it may be called any time before the first onDraw -- including * before or after {@link #onMeasure(int, int)}. * * @see #onDetachedFromWindow() */ protected void onAttachedToWindow() {
protected void onAttachedToWindow() { // Order is important here: LayoutDirection MUST be resolved before Padding // and TextDirection resolveLayoutDirectionIfNeeded(); resolvePadding(); resolveTextDirection(); if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); } }
3 fitSystemWindows(), 如果是系统window 则使用padding的值计算一下insets,并开始android.view.View.requestLayout()
protected boolean fitSystemWindows(Rect insets) { if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { mPaddingLeft = insets.left; mPaddingTop = insets.top; mPaddingRight = insets.right; mPaddingBottom = insets.bottom; requestLayout(); return true; } return false; }
在android.view.View.requestLayout()中,就是简单的一层一层向上检查parent是否存在,若存在调用parent的requestLayout();
/** * Call this when something has changed which has invalidated the * layout of this view. This will schedule a layout pass of the view * tree. */ public void requestLayout() { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT); } mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; if (mParent != null) { if (mLayoutParams != null) { mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); } if (!mParent.isLayoutRequested()) { mParent.requestLayout(); } } }但是每层调用完成,并不是立即执行layout操作,而是通过赋值标志位mPrivateFlags |= FORCE_LAYOUT;,来标识一下而已。真正的layout过程在后面。
4. 有一段代码要说一下。
if (mLayoutRequested && !mStopped) { // Execute enqueued actions on every layout in case a view that was detached // enqueued an action after being detached getRunQueue().executeActions(attachInfo.mHandler);RunQueue 是在handler 没有初始化的时候用来处理事件的消息队列。 把给ViewRootImpl post的事件 类型是runnable , 等到handler 构造好后,再发给handler 处理。不是本文的重点,就简单提一下。
5 接下来是measure 的部分
boolean goodMeasure = false; if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { // On large screens, we don't want to allow dialogs to just // stretch to fill the entire width of the screen to display // one line of text. First try doing the layout at a smaller // size to see if it will fit. final DisplayMetrics packageMetrics = res.getDisplayMetrics(); res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true); int baseSize = 0; if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { baseSize = (int)mTmpValue.getDimension(packageMetrics); } if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize); if (baseSize != 0 && desiredWindowWidth > baseSize) { childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); host.measure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { goodMeasure = true; } else { // Didn't fit in that size... try expanding a bit. baseSize = (baseSize+desiredWindowWidth)/2; if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize=" + baseSize); childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); host.measure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { if (DEBUG_DIALOG) Log.v(TAG, "Good!"); goodMeasure = true; } } } } if (!goodMeasure) { childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); host.measure(childWidthMeasureSpec, childHeightMeasureSpec); if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { windowSizeMayChange = true; } }这段代码基本就是在某些情况下 用特定的参数来measure , 另外一些情况下,用另外一些值来measure.
都是调用的host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 只是情况不同,使用的参数不同。
计算值的这部分参见前文《 源码分析:LayoutParams的wrap_content, match_parent, 和具体值》
然后就是调用measure()方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags &= ~MEASURED_DIMENSION_SET; if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE); } // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }在一些判断和标志位mPrivateFlags 赋值后,调用onMeasure() 方法。 onMeasure() 的讨论也请移步前文《 源码分析:LayoutParams的wrap_content, match_parent, 和具体值》
在方法的最后给标志位置位
mPrivateFlags |= LAYOUT_REQUIRED;
6 接着一大堆复杂的逻辑和赋值之后,调用了relayoutWindow() 这个还不太明白是怎么回事 // TODO
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
7 接着又是一堆逻辑和赋值,并在某种情况下还调用了host.measure()。算是measure 完成了
8. 然后是layout 的部分。
final boolean didLayout = mLayoutRequested && !mStopped; boolean triggerGlobalLayoutListener = didLayout || attachInfo.mRecomputeGlobalAttributes; if (didLayout) { mLayoutRequested = false; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());在layout() 中,先setFrame,然后判断状态标志位,进而回调onLayout(); 也就是自定义的部分。
public void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; if (mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~FORCE_LAYOUT; }
在setFrame()中,判断新的位置和旧的位置是否一致。
protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (DBG) { Log.d("View", this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; // Remember our drawn bit int drawn = mPrivateFlags & DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mPrivateFlags |= HAS_BOUNDS; if (sizeChanged) { if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { // A change in dimension means an auto-centered pivot point changes, too if (mTransformationInfo != null) { mTransformationInfo.mMatrixDirty = true; } } onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, thereby clearing // the DRAWN bit. mPrivateFlags |= DRAWN; invalidate(sizeChanged); // parent display list may need to be recreated based on a change in the bounds // of any child invalidateParentCaches(); } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; } return changed; }如果不一致,则调用invalidate();
void invalidate(boolean invalidateCache) { <span style="white-space:pre"> </span>if (p != null && ai != null) { final Rect r = ai.mTmpInvalRect; r.set(0, 0, mRight - mLeft, mBottom - mTop); // Don't call invalidate -- we don't want to internally scroll // our own bounds p.invalidateChild(this, r); } }调用父控件的p.invalidateChild() 来计算并标识 dirty 区域, 区域范围就是子view 所处的区域。
public void invalidateChild(View child, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); if (dirty == null) { // Fast invalidation for GL-enabled applications; GL must redraw everything invalidate(); return; } if (mCurScrollY != 0 || mTranslator != null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY != 0) { dirty.offset(0, -mCurScrollY); } if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } } if (!mDirty.isEmpty() && !mDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } mDirty.union(dirty); if (!mWillDrawSoon) { scheduleTraversals(); } }如果setframe 的返回值 为true ,则表示 区域的内容发生了变化进而回调onLayout() 方法,和mOnLayoutChangeListeners的onLayoutChange()。
/** * Called from layout when this view should * assign a size and position to each of its children. * * Derived classes with children should override * this method and call layout on each of * their children. * @param changed This is a new size or position for this view * @param left Left position, relative to parent * @param top Top position, relative to parent * @param right Right position, relative to parent * @param bottom Bottom position, relative to parent */ protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }在onLayout() 中, view应该通过调用每一个子view 的layout() 方法,来指定每一个子view 的大小和位置,。
9 接着有个computesInternalInsets的部分不太懂,sWindowSession.setInsets // TODO
if (computesInternalInsets) { // Clear the original insets. final ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets; insets.reset(); // Compute new insets in place. attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets); try { sWindowSession.setInsets(mWindow, insets.mTouchableInsets, contentInsets, visibleInsets, touchableRegion); } catch (RemoteException e) { } } }
10, 略去一部分处理过程,有焦点的回调和处理, 输入法的处理等。
11. 进入draw 的部分。
if (!cancelDraw && !newSurface) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } mFullRedrawNeeded = false; final long drawStartTime; if (ViewDebug.DEBUG_LATENCY) { drawStartTime = System.nanoTime(); } draw(fullRedrawNeeded); if (ViewDebug.DEBUG_LATENCY) { mLastDrawDurationNanos = System.nanoTime() - drawStartTime; } if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0 || mReportNextDraw) { if (LOCAL_LOGV) { Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle()); } mReportNextDraw = false; if (mSurfaceHolder != null && mSurface.isValid()) { mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( mSurfaceHolder); } } } } try { sWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { } }
在draw() 方法中,计算了dirty 的区域, 如果使用了硬件加速的话,进行相应的处理,否则使用canvas 来绘制view。
<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;"> private void draw(boolean fullRedrawNeeded) {</span><span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;"> mView.draw(canvas);</span>在android.view.View.draw(Canvas)中,绘制所有的子类
public void draw(Canvas canvas) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); } final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { final Drawable background = mBGDrawable; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; } if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); // we're done... return; }
注释写的很明白, 在step 1 中绘制背景, 在step 3 中调用自己的ondraw(); 在step 4 中 调用dispatchDraw() 绘制子view 。
最后调用sWindowSession.finishDrawing() 估计是通知底层完成吧,不太懂。//TODO
try { sWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { }
draw的部分到此完成
至此,一次完整的绘制流程就走完了,剩下的就是一遍遍的重复这个过程啦。回调过程参见前文《深入分析UI 上层事件处理核心机制 Choreographer》
尾巴
尾巴,接下来过一下事件的流程吧。