View体系中的遍历,简而言之就是系统综合考量各个元素请求的过程,当遍历结束之后,各个View元素就能得到系统的最终分配结果,android系统要求所有元素都服从它的安排,否则很可能会产生未知的错误,分配结果包含两个部分-View的大小和位置
1.ViewTree的遍历时机
1.1 应用程序刚启动时
应用程序启动后会逐步构建出自己的ViewTree,然后进行一次全面的遍历,setView中的requestLayout是执行第一次遍历的触发源,这个函数将通过向Choreographer注册一个CALLBACK_TRAVERSAL回调事件来驱动Layout的执行,最终的遍历由performTraversals来完成
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();//第一次遍历的触发源
}
1.2 外部事件
外部事件是ViewRoot工作的主要触发源,这些事件经过层层传递最终分配到应用进程中,这些事件有可能影响到UI界面的显示。
1.3 内部事件
除了外来触发源,程序在自身运行的过程中有时也需要主动发起一些触发事件。
2.实例
2.1 View的requestLayout方法
public void requestLayout() {
mPrivateFlags |= PFLAG_FORCE_LAYOUT;//PFLAG_FORCE_LAYOUT表明是程序自发的请求
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();//把请求往上一层传递,mParent可能是ViewGroup也可能是ViewRoot
}
}
ViewGroup没有重写requestLayout这个方法,所以会直接采用View类的requestLayout方法,如果mParent是ViewRoot的话,那么处理的方式如下
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//当前线程是否是主线程
mLayoutRequested = true;//当前已经发起layout请求
scheduleTraversals();//安排一次遍历
}
}
2.2 View的setLaoutParams方法
View的布局发生变化时,会主动申请一次遍历,如果mParent是ViewGroup,那就会回调onSetLayoutParams通知父类进行相应的调整,然后调用requestLayout方法
public void setLayoutParams(ViewGroup.LayoutParams params) {
mLayoutParams = params;
resolveLayoutParams();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onSetLayoutParams(this, params);
}
requestLayout();
}
2.3 View的invalidate方法
invalidate的意思是“使无效”,也就是将当前的UI界面判定为无效,从而引起重绘过程。这个方法只能从UI线程调用,其他线程可以通过postInvalidate达到同样的效果。
public void invalidate(Rect dirty);
public void invalidate(int l,int t,int r,int b);
public void invalidate();
public void invalidate(boolean invalidateCache);
public void invalidate(Rect dirty) {
if (skipInvalidate()) {//invalidate是否合法
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID ||
(mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) {
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags |= PFLAG_DIRTY;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
//noinspection PointlessBooleanExpression,ConstantConditions
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {//是否需要绘制整个区域
if (p != null && ai != null && ai.mHardwareAccelerated) {
//
p.invalidateChild(this, null);
return;
}
}
if (p != null && ai != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
final Rect r = ai.mTmpInvalRect;
r.set(dirty.left - scrollX, dirty.top - scrollY,
dirty.right - scrollX, dirty.bottom - scrollY);
//
mParent.invalidateChild(this, r);
}
}
}
TU 11-10 invalidate的调用流程
ViewGroup和ViewRoot中有同名的invalidateChild,但是他们所担负的责任却有很大的差异,前者是从下而上的,从当前点开始,沿着ViewTree回溯收集dirty区的过程,而后者是从上而下的,它真正法器ViewTree的遍历,从而实现画面的重绘。
2.4 dispatchAppVisibility
当可见性发生变化的时候,会调用这个函数,一旦ViewRootImpl收到Visibility改变的消息,也会组织一次Traversal;
3.总结
不论内部事件还是外部事件,只要ViewRoot在处理过程中发现它可能引起UI界面大小的或者位置的变化,就很可能执行遍历操作,遍历的主导者是ViewRootImpl,因为只有他才能自上而下的管理整棵ViewTree##
void scheduleTraversals() {
if (!mTraversalScheduled) {//当前是否正在做遍历
mTraversalScheduled = true; //当前正在做遍历
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
一旦VSYNC信号来临,mTraversalRunnable中的run方法将会被调用,会有序的组织ui的刷新
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;//变量的复位
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
try {
performTraversals();//执行遍历
} finally {
}
}
}
1.AMS
《内核设计》8
《开发艺术探索》9
《卷2》6
《内核剖析》10