Android View 绘制流程 与invalidate 和postInvalidate 分析--从源码角度
整个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 加进来的,分析其源码如下:
@Overridepublic voidsetContentView(View view,ViewGroup.LayoutParamsparams) {// 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);finalScene newScene =newScene(mContentParent,view);transitionTo(newScene);}else{
mContentParent.addView(view,params);}mContentParent.requestApplyInsets();finalCallback 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>**@paramchildthe child view to add*@paramparamsthe layout parameters to set on the child*/public voidaddView(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>**@paramchildthe child view to add*@paramindexthe position at which to add the child or -1 to add last*@paramparamsthe layout parameters to set on the child*/public voidaddView(View child, intindex,LayoutParams params) {if(DBG) { System.out.println(this+" addView");}if(child ==null) {throw newIllegalArgumentException("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 levelrequestLayout();invalidate(true);addViewInner(child,index,params, false);}
看见addView 的方法中调运invalidate方法,这不就真相大白了。
当我们写一个Activity时,我们一定会通过setContentView方法将我们要展示的界面传入该方法,该方法会讲通过addView追加到id为content的一个FrameLayout(ViewGroup)中,然后addView方法中通过调运invalidate(true)去通知触发ViewRootImpl类的performTraversals()方法,至此递归绘制我们自定义的所有布局。
最终会调用到ViewRootImpl的invalidate方法,从而调用 scheduleTraversals
voidinvalidate() {mDirty.set(0,0,mWidth,mHeight);if(!mWillDrawSoon) { scheduleTraversals();} }
进入到scheduleTraversals 放法中:
voidscheduleTraversals() {if(!mTraversalScheduled) {mTraversalScheduled=true;mTraversalBarrier=mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable, null);if(!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();} }
来看看 mTraversalRunnable实现了什么,其代码如下:
final classTraversalRunnableimplementsRunnable {@Overridepublic voidrun() { doTraversal();} }finalTraversalRunnablemTraversalRunnable=newTraversalRunnable();
voiddoTraversal() {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 voidperformMeasure(intchildWidthMeasureSpec, intchildHeightMeasureSpec) { 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>***@paramwidthMeasureSpecHorizontal space requirements as imposed by the* parent*@paramheightMeasureSpecVertical space requirements as imposed by the* parent**@see#onMeasure(int,int)*/public final voidmeasure(intwidthMeasureSpec, intheightMeasureSpec) {booleanoptical =isLayoutModeOptical(this);if(optical !=isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets();intoWidth = insets.left + insets.right;intoHeight = insets.top + insets.bottom;widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec,optical ? -oWidth : oWidth);heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec,optical ? -oHeight : oHeight);}// Suppress sign extension for the low byteslongkey = (long) widthMeasureSpec <<32| (long) heightMeasureSpec &0xffffffffL;if(mMeasureCache==null)mMeasureCache=newLongSparseLongArray(2);if((mPrivateFlags&PFLAG_FORCE_LAYOUT) ==PFLAG_FORCE_LAYOUT|| widthMeasureSpec !=mOldWidthMeasureSpec|| heightMeasureSpec !=mOldHeightMeasureSpec) {// first clears the measured dimension flagmPrivateFlags&= ~PFLAG_MEASURED_DIMENSION_SET;resolveRtlPropertiesIfNeeded();intcacheIndex = (mPrivateFlags&PFLAG_FORCE_LAYOUT) ==PFLAG_FORCE_LAYOUT? -1:mMeasureCache.indexOfKey(key);if(cacheIndex <0||sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag backonMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3&= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}else{longvalue =mMeasureCache.valueAt(cacheIndex);// Casting a long to int drops the high 32 bits, no mask neededsetMeasuredDimensionRaw((int) (value >>32),(int) value);mPrivateFlags3|=PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}// flag not set, setMeasuredDimension() was not invoked, we raise// an exception to warn the developerif((mPrivateFlags&PFLAG_MEASURED_DIMENSION_SET) !=PFLAG_MEASURED_DIMENSION_SET) {throw new
