一、setContentView机制分析
setContentView在window.java中是个抽象方法,具体实现是在phonewindow.java
当我们写一个Activity时,我们一定会通过setContentView方法将我们要展示的界面传入该方法,该方法会讲我们界面通过addView追加到id为content的一个FrameLayout(ViewGroup)中,然后addView方法中通过调运invalidate(true)去通知触发ViewRootImpl类的performTraversals()方法,至此递归绘制我们自定义的所有布局。
1.1介绍invalidate()方法
只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View
所有的invalidate最终会调用View类中的invalidateInternal()
invalidateInternal方法测量了绘制区域后调用parent的invalidateChild(),View的invalidate(invalidateInternal)方法实质是将要刷新区域直接传递给了父ViewGroup的invalidateChild方法,在invalidate中,调用父View的invalidateChild,这是一个从当前向上级父View回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集 。看下ViewGroup的invalidateChild方法,
这个过程最后传递到ViewRootImpl的invalidateChildInParent方法结束,所以我们看下ViewRootImpl的invalidateChildInParent方法,如下:
scheduleTraversals会通过Handler的Runnable发送一个异步消息,调运doTraversal方法,然后最终调用performTraversals()执行重绘。
1.2 postInvalidate方法
上面分析invalidate方法时注释中说该方法只能在UI Thread中执行,其他线程中需要使用postInvalidate方法,如下:
通过ViewRootImpl类的Handler发送了一条MSG_INVALIDATE消息,实质就是又在UI Thread中调运了View的invalidate();方法。
1.3
归纳出重点:
invalidate系列方法请求重绘View树(也就是draw方法),如果View大小没有发生变化就不会调用layout过程,并且只绘制那些“需要重绘的”View,也就是哪个View(View只绘制该View,ViewGroup绘制整个ViewGroup)请求invalidate系列方法,就绘制该View。
常见的引起invalidate方法操作的原因主要有:
- 直接调用invalidate方法.请求重新draw,但只会绘制调用者本身。
- 触发setSelection方法。请求重新draw,但只会绘制调用者本身。
- 触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。
- 触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。
- 触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。
2.1requestLayout方法分析
和invalidate类似,其实在上面分析View绘制流程时或多或少都调运到了这个方法,而且这个方法对于View来说也比较重要,
当我们触发View的requestLayout时其实质就是层层向上传递,直到ViewRootImpl为止,然后触发ViewRootImpl的requestLayout方法,如下就是ViewRootImpl的requestLayout方法:
2.2requestLayout方法总结
requestLayout()方法会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身。
二、View的绘制流程
Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View实现的,每一个View的绘制过程都必须经历三个最主要的过程,也就是measure、layout和draw。
整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法,该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个
注释说是用来测Root View的。上面传入参数后这个函数走的是MATCH_PARENT,使用MeasureSpec.makeMeasureSpec方法组装一个MeasureSpec,MeasureSpec的specMode等于EXACTLY,specSize等于windowSize,也就是为何根视图总是全屏的原因。