Android 特定情况requestlayout 一直无效分析

问题的发现

在做一次项目重构后,发现偶现ViewPager页面出现滑动到一半停住,继续触摸继续滑动,连续滑动后,ViewPager不会有新的页面创建,所有页面都滑到了屏幕之外。

问题分析

既然问题出现在ViewPager,那么当然是从ViewPager的代码开始分析了,既然Viewpager可以滑动,但是松手的时候一切没有触发新页面的加载,于是我怀疑是Viewpager的populate(int item)方法有问题。

经过断点分析,发现这段逻辑完全正常,松手后,确实完美触发了populate 方法,index同样也是对的,那么为什么没有触发滚动动画和页面加载呢。

重新增加断点到Viewpager adapter里的View创建里,发现正常情况下,创建页面的调用栈是从View的onlayout触发的,而异常情况下,没有触发onLyaout。

wtf,调用了n次requestlayout,一次layout都没触发,这是什么操作,查看requestlayout代码

重新debug发现事件向ViewRootImpl的传递断在了mParent.isLayoutRequested ,也就是说父亲已经调用过requestLayout 了,但是既然父亲已经requestLayout那么为什么也没有触发layout呢

分析带有PFLAG_FORCE_LAYOUT 方法,主要是在Measure和layout中。与之关联的还有一个flag PFLAG_LAYOUT_REQUIRED

在measure中主要是以下逻辑

在layou中主要是以下逻辑

总结以上代码

PFLAG_FORCE_LAYOUT = 请求进行布局

PFLAG_LAYOUT_REQUIRED = 确定需要重新布局

当调用了requestLayout时,会设置PFLAG_FORCE_LAYOUT

PFLAG_FORCE_LAYOUT 会在onMeasure时候处理,并且会在在真正需要进行layout的情况打上新的Flag PFLAG_LAYOUT_REQUIRED

PFLAG_LAYOUT_REQUIRED 和 PFLAG_FORCE_LAYOUT 都将在调用Layout的时候进行清空

不难发现,如果当一个View的measure方法刚刚完成,但是还没进行Layout的情况下,如果调用了该View的requestLayout,会造成没有置上 PFLAG_LAYOUT_REQUIRED ,使得没有真正进行layout但是PFLAG_FORCE_LAYOUT已经被清空,此时对客户端的表现来说,就是调用了 requestLayout 但是布局没有变,并且isLayoutRequested() == false,由于本次调用没有触发Layout,更严重的情况会导致子控件的 isLayoutRequested() == true,子控件的子控件会由于 父亲的isLayoutRequested()一直为true,阻断子控件的向上传递的 requestLayout()

总结

假如存在Group,A,B,和AA四个控件 group包含两个控A和B,A在B的前面, A包含子控件AA :则发生以下操作可以触发bug

如果在B的meaure的时候调用AA的 requestLayout,此时A已经调用过 meaure,并且确定不触发onLayout,但后续A的 requestLayout flag会被清空,由于A不触发 onLayout ,AA的 requestLayout flag会继续存在。

此时出现错误:AA的父亲A requestLayout = false AA 的 requestLayout =true,如果AA还有子控件,则它所有layout请求失效,直到A重新触发OnLayout

一句话,假如你是一个控件,在你哥哥一脉这边,你调用你侄子的 requestLayout,那么侄孙就没法 requestLayout 了

有时候觉得正常操作不会出现这个情况,但是现在由于enventbus rxjava到处飞,所以有时候搞不清事件究竟从哪过来的,是有可能触发这种场景的