View的三次measure,两次layout和一次draw

 我在《Android视图结构》这篇文章中已经描述了Activity,WindowView在视图架构方面的关系。前天,我忽然想到为何在setContentView中可以调用findViewById函数?View那时不是尚未被加载,测量,布局和绘制啊。而后就搜索了相关的条目,发现findViewById只须要在inflate结束以后就能够。因而,我整理了Activity生命周期和View的生命周期的关系,并再次作一下总结。java

 为了节约你的时间,本篇文章的主要内容为:git

  • Activity的生命周期和它包含的View的生命周期的关系github

  • Activity初始化时View为何会三次measure,两次layout但只一次draw?windows

  • ViewRoot的初始化过程架构

Activity的生命周期和View的生命周期

 我经过一个简单的demo来验证Activity生命周期方法和View的生命周期方法的调用前后顺序。请看以下截图ide

截图

 在onCreate函数中,咱们一般都调用setContentView来设置布局文件,此时Android系统就会读取布局文件,可是视图此时并无加载到Window上,而且也没有进入本身的生命周期。
 只有等到Activity进入resume状态时,它所拥有的View才会加载到Window上,并进行测量,布局和绘制。因此咱们会发现相关函数的调用顺序是:函数

  • onResume(Activity)布局

  • onPostResume(Activity)学习

  • onAttachedToWindow(View)spa

  • onMeasure(View)

  • onMeasure(View)

  • onLayout(View)

  • onSizeChanged(View)

  • onMeasure(View)

  • onLayout(View)

  • onDraw(View)

 你们会发现,为何onMeasure先调用了两次,而后再调用onLayout函数,最后还有在依次调用onMeasure,onLayoutonDraw函数呢?

ViewGroup的measure

 你们应该都知道,有些ViewGroup可能会让本身的子视图测量两次。好比说,父视图先让每一个子视图本身测量,使用View.MeasureSpec.UNSPECIFIED,而后在根据每一个子视图但愿获得的大小不超过父视图的一些限制,就让子视图获得本身但愿的大小,不然就用其余尺寸来从新测量子视图。这一类的视图有FrameLayout,RelativeLayout等。
 在《Android视图结构》中,咱们已经知道Android视图树的根节点是DecorView,而它是FrameLayout的子类,因此就会让其子视图绘制两次,因此onMeasure函数会先被调用两次。

// FrameLayout的onMeasure函数,DecorView的onMeasure会调用这个函数。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    .....
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            ......
        }
    }
    ........
    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            ........
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

 你觉得到了这里就能解释通View初始化时的三次measure,两次layout却只一次draw吗?那你就太天真了!咱们都知道,视图结构中不只仅是DecorViewFrameLayout,还有其余的须要两次measure子视图的ViewGroup,若是每次都致使子视图两次measure,那效率就过低了。因此Viewmeasure函数中有相关的机制来防止这种状况。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ......
  // 当FLAG_FORCE_LAYOUT位为1时,就是当前视图请求一次布局操做
  //或者当前当前widthSpec和heightSpec不等于上次调用时传入的参数的时候
  //才进行重新绘制。
    if (forceLayout || !matchingSize &&
            (widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec)) {
            ......
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            ......
    }
    ......
}

 源码看到这里,我几乎失望的眼泪掉下来!没办法,只能再想其余的方法来分析这个问题。

断点调试大法好

 为了分析函数调用的层级关系,我想到了断点调试法。因而,我果断在onMeasureonLayout函数中设置了断点,而后进行调试。

函数调用帧

 在《Android视图架构》一文中,咱们知道ViewRootDecorView的父视图,虽然它本身并非一个View,可是它实现了ViewParent的接口,Android正是经过它来实现整个视图系统的初始化工做。而它的performTraversals函数就是视图初始化的关键函数。
 对比两次onMeasure被调用时的函数调用帧,咱们能够轻易发现ViewRootImplperformTraversals函数中直接和间接的调用了两次performMeasure函数,从而致使了View最开始的两次measure过程。
 而后在相同的performTraversals函数中会调用performLayout函数,从而致使View进行一轮layout过程。
 可是为何此次performTraversals并无触发View的draw过程呢?反而是View又将从新进行一轮measure,layout过程以后才进行draw。

两次performTraversals

 经过断点调试,咱们发如今View初始化的过程当中,系统调用了两次performTraversals函数,第一次performTraversals函数致使了View的前两次的onMeasure函数调用和第一次的onLayout函数调用。后一次的performTraversals函数致使了最后的onMeasure,onLayout,和onDraw函数的调用。可是,第二次performTraversals为何会被触发呢?咱们研究一下其源码就可知道。

private void performTraversals() {
    ......
    boolean newSurface = false;
    //TODO:决定是否让newSurface为true,致使后边是否让performDraw没法被调用,而是从新scheduleTraversals
    if (!hadSurface) {
        if (mSurface.isValid()) {
            // If we are creating a new surface, then we need to
            // completely redraw it.  Also, when we get to the
            // point of drawing it we will hold off and schedule
            // a new traversal instead.  This is so we can tell the
            // window manager about all of the windows being displayed
            // before actually drawing them, so it can display then
            // all at once.
            newSurface = true;
                    .....
        }
    }
            ......
    if (!cancelDraw && !newSurface) {
        if (!skipDraw || mReportNextDraw) {
            ......
            performDraw();
        }
    } else {
        if (viewVisibility == View.VISIBLE) {
            // Try again
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }
    ......
}

 由源代码能够看出,当newSurface为真时,performTraversals函数并不会调用performDraw函数,而是调用scheduleTraversals函数,从而再次调用一次performTraversals函数,从而再次进行一次测量,布局和绘制过程。
 咱们由断点调试能够轻易看到,第一次performTraversals时的newSurface为真,而第二次时是假。

断点调试截图

总结

 虽然我已经经过源码得知View初始化时measure三次,layout两次,draw一次的缘由,可是Android系统设计时,为何要将整个初始化过程设计成这样?我却尚未明白,为何当Surface为新的时候,要推迟绘制,从新进行一轮初始化,这些可能都要涉及到Surface的相关内容,我以后要继续学习相关内容!

相关文章
相关标签/搜索