美文网首页
子线程requestLayout导致界面显示异常-问题总结

子线程requestLayout导致界面显示异常-问题总结

作者: WLHere | 来源:发表于2020-09-17 20:43 被阅读0次

正常的requestLayout流程

  1. 调用requestLayout()
  2. 设置标识PFLAG_FORCE_LAYOUT,标明请求了requestLayout
  3. 依次向上请求parent的requestLayout。如果parent已经请求过了,则不再向上请求
  4. 向上直到调用到ViewRootImpl的requestLayout方法。ViewRootImpl实现了ViewParent方法,而它是布局树顶点DecorView的parent
  5. ViewRootImpl执行requestLayout
    1. 检查线程
    2. 设置mLayoutRequested为true
    3. scheduleTraversals
  6. 待下一个vsync信号到来,执行performTraversals方法
  7. ViewRootImpl执行performTraversals。如果mLayoutRequested为true则调用mView.layout方法执行layout流程
  8. View.layout执行layout并重置PFLAG_FORCE_LAYOUT标志位。
  9. 界面正常显示

View.java

public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
        // 设置标识,标明请求了requestLayout
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
        
        // 依次向上请求parent的requestLayout。如果parent已经请求过了,则不再向上请求
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
  
  // 是否请求了requestLayout
  public boolean isLayoutRequested() {
        return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    }

ViewRootImpl.java

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            // 如果不是主线程,这里会抛出异常
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

void checkThread() {
        // mThread在ViewRootImpl的构造函数赋值mThread = Thread.currentThread()。而ViewRootImpl是在主线程创建的,所以mThread指向主线程
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

异常的requestLayout流程

  1. 子线程执行requestLayout,并在ViewRootImpl.reqeustLayout抛出异常。在checkThread()因为抛出异常没有设置mLayoutRequested = true,也没有scheduleTraversals,此次requestLayout终止。
  2. 因为View调用requestLayout时一路向上设置了PFLAG_FORCE_LAYOUT,因为没有发生layout所以此标志位一直是1。后续调用requetLayout因为标志位PFLAG_FORCE_LAYOUT是1,所以不会继续向上调用requestLayout。
  3. 没有requestLayout无法触发layout,后续add的view都无法显示出来,页面显示异常了

为什么切到后台后再切回前台,就恢复正常显示了?

切到后台再切回前台,触发scheduleTraversals。在performTraversals方法里边,因为可见性发生了改变,会设置layoutRequested为true,触发mView.layout,完成layout流程,也会把PFLAG_FORCE_LAYOUT置为0,后续requestLayout也就正常了。

ViewRootImpl.W

static class W extends IWindow.Stub {
    private final WeakReference<ViewRootImpl> mViewAncestor;
    private final IWindowSession mWindowSession;

    W(ViewRootImpl viewAncestor) {
        mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
        mWindowSession = viewAncestor.mWindowSession;
    }

    ///////// 

    @Override
    public void dispatchAppVisibility(boolean visible) {
        final ViewRootImpl viewAncestor = mViewAncestor.get();
        if (viewAncestor != null) {
            viewAncestor.dispatchAppVisibility(visible);
        }
    }

    /////////
}

相关文章

网友评论

      本文标题:子线程requestLayout导致界面显示异常-问题总结

      本文链接:https://www.haomeiwen.com/subject/zqjryktx.html