本文转载自:https://www.jianshu.com/p/733c7e9fb284
前言
郭前辈的ListView源码解析一文,曾提到View至少会进行2次onMeasure、onLayout,但限于篇幅,并未解释原因,好奇就尝试找了找原因。
原因猜想
害怕.jpg
由于不知道具体原因,只能结合已有的知识,先做出如下猜想:
- View自身进行了2次onMeasure、onLayout
- ViewGroup对Child进行了2次measure、layout
- 我们知道View的绘制流程都始于ViewRootImpl的performTraversals方法,有理由怀疑performTranversals执行了2次。
PS:赶时间的朋友,可直接阅读验证三
验证一、二
按照上面的猜想,先进入View类查找总共就2处调用了onMeasure方法如下:
1 | public final void measure(int widthMeasureSpec, int heightMeasureSpec) { |
第二处:
1 | public void layout(int l, int t, int r, int b) { |
第二次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 | //1.由于第一次执行newSurface必定为true,需要先创建Surface嘛 |
断点SDK-23源码结果如下图所示:
PS:断点源码建议使用模拟器,真机一般都是修改过的,与SDK代码不一致。
第一次performTranversals
第二次performTranversals
既然确定了performTravelsals会执行2次,那么肯定会执行2次measure方法,但是执行2次measure方法就一定会执行2次onMeasure方法吗?
答案是否定,分析过View measure方法源码的都应知道measure方法做了2级测量优化:
- 如果flag不为forceLayout或者与上次测量规格(MeasureSpec)相比未改变,那么将不会进行重新测量(执行onMeasure方法),直接使用上次的测量值;
- 如果满足非强制测量的条件,即前后二次测量规格不一致,会先根据目前测量规格生成的key索引缓存数据,索引到就无需进行重新测量;如果targetSDK小于API 20则二级测量优化无效,依旧会重新测量,不会采用缓存测量值。
照理第二次测量应该会取测量的缓存值,并不会重新测量(调用onMeasure)的。然而实际上确重新测量了,那么极有可能就是第二次performMeasure传入的测量规格与第一次不同,因为在layout执行中已经将flag force_layout置为false了,代码如下:
1 | public void layout(int l, int t, int r, int b) { |
按照刚才的分析,前后二次的传入的测量规格应该不一致,然而事实是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次。