從前文《 源碼解析:dialog, popupwindow, 和activity 的第一個view是怎麼來的?》中知道了activity第一個view或者說根view或者說mDecorView 其實就是一個FrameLayout,以及是在系統handleResume的時候加入到系統windowManager中的,並由framework中的ViewRootImpl 接管,通過ViewRootImpl.setView() 開始整個顯示過程的。這次著重梳理一下view的顯示過程(onAttach, onMeasure, onLayout, onDraw )在源碼中的過程。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; requestLayout(); 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(); }進而就是performTraversals(),也就是本次分析的重點。
這個函數比較長,不適合把全部函數代碼都 貼上來。就分段敘述。
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;
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在這裡onAttachedToWindow() 的注釋帶來了一個問題:僅保證會在onDraw 前調用,而不保證在onMeasure 之前或者之後調用 onAttachedToWindow。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); } }
/** * 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); } }
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過程在後面。
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 處理。不是本文的重點,就簡單提一下。
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;
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
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) { ArrayListlistenersCopy = (ArrayList ) 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) { 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 的大小和位置,。
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) { } } }
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) { } }
private void draw(boolean fullRedrawNeeded) { mView.draw(canvas);在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; }
try { sWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { }
draw的部分到此完成
至此,一次完整的繪制流程就走完了,剩下的就是一遍遍的重復這個過程啦。回調過程參見前文《深入分析UI 上層事件處理核心機制 Choreographer》