从前文《 源码解析: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();
- }
- }
- public void requestLayout() {
- checkThread();
- mLayoutRequested = true;
- 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);
- }
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case DO_TRAVERSAL:
- performTraversals();
- span style="white-space:pre"> </span>}
这个函数比较长,不适合把全部函数代码都 贴上来。就分段叙述。
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;
- }
- }
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);
- }
- }
- /**
- * 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();
- }
- }
- }
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);
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;
- }
- }
都是调用的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 |= 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());
- 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;
- }
- 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);
- }
- }
- 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();
- }
- }
- /**
- * 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) {
- }
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>
- 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》