整个View树的绘制流程是在ViewRootImpl.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为
根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要布局视图的位置(layout)、以及是否需要重绘
(draw),所以整个View的绘制过程,总结为三步:
1、 Measure:测量View大小
2 、 Layout:对View进行布局
3、 Draw:绘制View:View背景、View内容、View边线、绘制子View(如果有);
一、performTraversals() 函数触发时机
在这里提问下,为什么View的绘制是从performTraversals() 函数开始,这个函数是在什么时候触发的呢?
来通过源码来分析下到底是什么原因,本文的源码是基于最新的源码6.0.1
首先我们知道是通过 PhoneWindow的 setContentView 将View 加进来的,分析其源码如下:
@Override public void setContentView(View view, ViewGroup.LayoutParams params) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { view.setLayoutParams(params); final Scene newScene = new Scene(mContentParent, view); transitionTo(newScene); } else {
mContentParent.addView(view, params); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
然后调用到ViewGroup 的addView 方法:
/** * Adds a child view with the specified layout parameters. * * <p><strong>Note:</strong> do not invoke this method from * { @link #draw(android.graphics.Canvas)}, { @link #onDraw(android.graphics.Canvas)}, * { @link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> * * @param child the child view to add * @param params the layout parameters to set on the child */ public void addView(View child, LayoutParams params) { addView(child, -1, params); }
/** * Adds a child view with the specified layout parameters. * * <p><strong>Note:</strong> do not invoke this method from * { @link #draw(android.graphics.Canvas)}, { @link #onDraw(android.graphics.Canvas)}, * { @link #dispatchDraw(android.graphics.Canvas)} or any related method.</p> * * @param child the child view to add * @param index the position at which to add the child or -1 to add last * @param params the layout parameters to set on the child */ public void addView(View child, int index, LayoutParams params) { if (DBG) { System.out.println(this + " addView"); } if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } // addViewInner() will call child.requestLayout() when setting the new LayoutParams // therefore, we call requestLayout() on ourselves before, so that the child's request // will be blocked at our level requestLayout(); invalidate(true); addViewInner(child, index, params, false); }
看见addView 的方法中调运invalidate方法,这不就真相大白了。
当我们写一个Activity时,我们一定会通过setContentView方法将我们要展示的界面传入该方法,该方法会讲通过addView追加到id为content的一个FrameLayout(ViewGroup)中,然后addView方法中通过调运invalidate(true)去通知触发ViewRootImpl类的performTraversals()方法,至此递归绘制我们自定义的所有布局。
最终会调用到ViewRootImpl的invalidate方法,从而调用 scheduleTraversals
void invalidate() { mDirty.set(0, 0, mWidth, mHeight); if (!mWillDrawSoon) { scheduleTraversals(); } }
进入到scheduleTraversals 放法中:
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
来看看 mTraversalRunnable实现了什么,其代码如下:
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
至此,对于为什么View的绘制入口是在performTraversals 本文分析完成;
二、 View绘制三大过程的具体分析
接下来,来具体分析下View绘制的三大过程,即performTraversals 做了具体什么事情,其源码如下:
private void performTravelsals(){
....
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height)
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
int desiredWindowWidth;
int desiredWindowHeight;
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
performDraw();
}
从上述源码分析,也验证来了文章开头分析的View 绘制过程的三大步的正确性;
2.1 Measure 过程分析
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); } }
/** * <p> * This is called to find out how big a view should be. The parent * supplies constraint information in the width and height parameters. * </p> * * <p> * The actual measurement work of a view is performed in * { @link #onMeasure(int, int)}, called by this method. Therefore, only * { @link #onMeasure(int, int)} can and must be overridden by subclasses. * </p> * * * @param widthMeasureSpec Horizontal space requirements as imposed by the * parent * @param heightMeasureSpec Vertical space requirements as imposed by the * parent * * @see #onMeasure(int, int) */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int oWidth = insets.left + insets.right; int oHeight = insets.top + insets.bottom; widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); } // Suppress sign extension for the low bytes long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCa