View为什么会至少进行2次onMeasure、onLayout

前言

郭前辈的ListView源码解析一文,曾提到View至少会进行2次onMeasure、onLayout,但限于篇幅,并未解释原因,好奇就尝试找了找原因。

原因猜想

害怕.jpg

由于不知道具体原因,只能结合已有的知识,先做出如下猜想:
1.View自身进行了2次onMeasure、onLayout
2.ViewGroup对Child进行了2次measure、layout
3.我们知道View的绘制流程都始于ViewRootImpl的performTraversals方法,有理由怀疑performTranversals执行了2次。
PS:赶时间的朋友,可直接阅读验证三

验证一、二

按照上面的猜想,先进入View类查找总共就2处调用了onMeasure方法如下:

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 .........
  final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
 .........
  int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
  if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  }
.........
  }

第二处:

public void layout(int l, int t, int r, int b) {
  if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
   ..........
}
 /**
     * Flag indicating that a call to measure() was skipped and should be done
     * instead when layout() is invoked.
     */
    static final int PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT = 0x8;

第二次onMeasure,但是注释说的很明确,只有当measure方法未被调用的时候,才会在layout里面执行一次onMeasure方法,正常的view树测量流程,每个view的measure方法确实的都被调用过,所以猜想一排除。

关于猜想二,以FrameLayout为例,确实是在onMeasure方法中对child进行了2次测量,但这是有条件限制的,需要FrameLayout的layout_width/height属性不能为match_parent或具体的值,且child的layout属性必须为match_parent,具有特殊性,实际上即使不满足以上条件依旧会进行2次测量,故排除猜想二。

PS:关于一、二的源码分析,可参考View measure源码分析

验证三

看了看代码,发现会执行2次performTranversals,也就会执行2次测量。
ViewRootImpl#performTraversals()代码片段一

        //1.由于第一次执行newSurface必定为true,需要先创建Surface嘛
        //为true则会执行else语句,所以第一次执行并不会执行 performDraw方法,即View的onDraw方法不会得到调用
        //第二次执行则为false,并未创建新的Surface,第二次才会执行 performDraw方法
        if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }

                performDraw();
            }
        } else {
            //2.viewVisibility是wm.add的那个View的属性,View的默认值都是可见的
            if (viewVisibility == View.VISIBLE) {
                // Try again
                //3.再执行一次 scheduleTraversals,也就是会再执行一次performTraversals
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

断点SDK-23源码结果如下图所示:
PS:断点源码建议使用模拟器,真机一般都是修改过的,与SDK代码不一致。

第一次performTranversals
第二次performTranversals

既然确定了performTravelsals会执行2次,那么肯定会执行2次measure方法,但是执行2次measure方法就一定会执行2次onMeasure方法吗?
答案是否定,分析过View measure方法源码的都应知道measure方法做了2级测量优化:
1.如果flag不为forceLayout或者与上次测量规格(MeasureSpec)相比未改变,那么将不会进行重新测量(执行onMeasure方法),直接使用上次的测量值;
2.如果满足非强制测量的条件,即前后二次测量规格不一致,会先根据目前测量规格生成的key索引缓存数据,索引到就无需进行重新测量;如果targetSDK小于API 20则二级测量优化无效,依旧会重新测量,不会采用缓存测量值。

照理第二次测量应该会取测量的缓存值,并不会重新测量(调用onMeasure)的。然而实际上确重新测量了,那么极有可能就是第二次performMeasure传入的测量规格与第一次不同,因为在layout执行中已经将flag force_layout置为false了,代码如下:

  public void layout(int l, int t, int r, int b) {
        .........
        //mPrivateFlags第16位设置为0,0表示不强制layout
        //PFLAG_FORCE_LAYOUT = 0x00001000
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

按照刚才的分析,前后二次的传入的测量规格应该不一致,然而事实是2次传入onMeasure()的测量规格一致,结果如下:

log信息

那么问题又来了,为什么会测量三次呢?首先声明的是,并不是因为FrameLayout的多次测量,此处的自定义View并不满足FrameLayout测量2次child的条件。经过断点跟踪SDK源码发现:
第一次performTranversals会执行2次performMeasure:
先执行measureHierarchy方法中的performMeasure方法

第一次执行处
方法调用栈

接着执行后面的performMeasure,

第二次执行处

方法调用栈

第二次performTranversals则是只执行measureHierarchy中的performMeasure方法
这就能解释为什么前2次测量都执行了onMeasure方法,而未采用测量优化策略,因为前2次performMeasure并未经过performLayout,也即forceLayout的标志位一直为true,自然不会取缓存优化。理论上第三次测量经过第一次performTranversals中的performLayout,强制layout的flag应该为false,然而实际上却又变成了true,至于在哪儿恢复为true的,我在源码中并没有找到答案。
但是这在api24、25上却及其符合我们的推论,第三次强制layout的flag为false,即第二次performTranversals并不会导致View的onMeasure方法的调用,由于未调用onMeasure方法,也不会调用onLayout方法,即api 25只会执行2次onMeasure、一次onLayout、一次onDraw,如下图所示:

SDK-24
SDK-25

总结:

api25-24:执行2次onMeasure、2次onLayout、1次onDraw,理论上执行三次测量,但由于测量优化策略,第三次不会执行onMeasure。
api23-21:执行3次onMeasure、2次onLayout、1次onDraw,forceLayout标志位,离奇被置为true,导致无测量优化。
api19-16:执行2次onMeasure、2次onLayout、1次onDraw,原因第一次performTranversals中只会执行measureHierarchy中的performMeasure,forceLayout标志位,离奇被置位true,导致无测量优化。
总之,造成这个现象的根本原因是performTranversal函数在View的测量流程中会执行2次。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容