View体系5:ViewTree的遍历时机

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

你可能感兴趣的:(View体系5:ViewTree的遍历时机)