作者:字节小站
背景
最近有个星标粉丝跟我提了一个很有深度的问题。
❝
粉丝:锁屏后,调用View.requestLayout()方法后会不会postSyncBarrier?
❞
乍一看有点超纲了。细细一想,没超纲。我把这个问题拆分成了两个问题,本文我将紧紧围绕这两个问题,讲解requestLayout背后的故事。
❝
「其一:锁屏后,调用View.requestLayout(),会往上层层调用requestLayout()吗?」
「其二:锁屏后,调用View.requestLayout(),会触发View的测量和布局操作吗?」
❞
postSyncBarrier我知道,Handler的同步屏障机制嘛,但是锁屏之后为什么还要调用requestLayout()呢?于是我脑补了一个场景。
❝
假设在Activity onResume()中每隔一秒调用View.requestLayout(),但是在onStop()方法中没有停止调用该方法。当用户锁屏或者按Home键时。
❞
我脑补的这个场景,用罗翔老师的话来讲是 「“法律允许,但是不提倡”」。当Activity不在前台的时候,就应该把requestLayout()方法停掉嘛,「我们知道的,这个方法会从调用的View一层一层往上调用直到ViewRootImpl.requestLayout()方法,然后会从上往下触发View的测量和布局甚至绘制方法。非常之浪费嘛!错误非常之低级!但是果真如此吗?(偷偷告诉大家,其实一直调用也没关系,Google大神已经考虑到了,不信且看后文)」
有一句网络流行语「你以为我在第一层,其实我在第十层」。
下面我将用层级来表示对requestLayout
方法的了解程度,层级越高,表示了解越深刻。
我喜欢用树形图来分析Android View源码。上图:
第一层(往上,层层遍历)
假设调用I.requestLayout(),会触发哪些View的requestLayout方法?
答:会依次触发I.requestLayout() -> C.requestLayout() -> A.requestLayout()-> …省略一些View -> ViewRootImpl.requestLayout()。
public void requestLayout() {
// 1. 清除测量记录
if (mMeasureCache != null) mMeasureCache.clear();
// 2. 增加PFLAG_FORCE_LAYOUT给mPrivateFlags
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
// 3\. 如果mParent没有调用过requestLayout,则调用之。换句话说,如果调用过,则不会继续调用
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
该方法作用如下:
- 清除测量记录。
- 增加PFLAG_FORCE_LAYOUT给mPrivateFlags。
- 如果mParent没有调用过requestLayout,则调用之。换句话说,如果调用过,则不会继续调用。
重点看下mParent.isLayoutRequested()方法,它在View.java中有具体实现:
//View.java
public boolean isLayoutRequested() {
return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
}
如果mPrivateFlags增加了PFLAG_FORCE_LAYOUT标志位,则认为View已经请求过布局。由前文可知,在requestLayout的第二步会增加该标志位。「熟悉位操作的朋友就会知道,有增加操作就会有对应的清除操作。」 经过一番搜索,找到:
//View.java
public void layout(int l, int t, int r, int b) {
// ... 省略代码
//在View调用完layout方法,会将PFLAG_FORCE_LAYOUT标志位清除掉
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
// ...