子线程更新主线程的View抛出异常全过程

上篇文章:Android子线程真的不能刷新UI吗?(一)复现异常,复现了子线程修改UI的异常。这篇文章,详细跟踪setText方法,是怎么导致抛出异常的。

子线程更新主线程的View会有什么后果?

会抛异常

过程具体是怎样的?

24698-24800/com.zj.androidthreaddemo E/AndroidRuntime: FATAL EXCEPTION: Thread-3
    Process: com.zj.androidthreaddemo, PID: 24698
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8632)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1380)
        at android.view.View.requestLayout(View.java:23377)
        at android.view.View.requestLayout(View.java:23377)
        at android.view.View.requestLayout(View.java:23377)
        at android.view.View.requestLayout(View.java:23377)
        at android.view.View.requestLayout(View.java:23377)
        at android.view.View.requestLayout(View.java:23377)
        at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3239)
        at android.view.View.requestLayout(View.java:23377)
        at android.widget.TextView.checkForRelayout(TextView.java:9221)
        at android.widget.TextView.setText(TextView.java:5935)
        at android.widget.TextView.setText(TextView.java:5776)
        at android.widget.TextView.setText(TextView.java:5733)
        at com.zj.androidthreaddemo.MainActivity$1.run(MainActivity.java:23)
        at java.lang.Thread.run(Thread.java:784)

TextView.setText()源码:

    public final void setText(CharSequence text) {
    
    
  		//调用重载
        setText(text, mBufferType);
    }
	
    public void setText(CharSequence text, BufferType type) {
    
    
        //再次调用重载
        setText(text, type, true, 0);

        if (mCharWrapper != null) {
    
    
            mCharWrapper.mChars = null;
        }
    }

    private void setText(CharSequence text, BufferType type,
                         boolean notifyBefore, int oldlen) {
    
    


        if (mLayout != null) {
    
    
        	//在这里调用了checkForRelayout
            checkForRelayout();
        }

		...
    }

TextView.checkForRelayout的作用是什么?

android 6.0 /frameworks/base/core/java/android/widget/TextView.java

    private void checkForRelayout() {
    
    
      
        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
                (mHint == null || mHintLayout != null) &&
                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
    
    
           ......
        } else {
    
    
           
            nullLayouts();
            //在这里调用了requestLayout()
            requestLayout();
            invalidate();
        }
    }

调用View.requestLayout()
Android子线程与更新UI问题的深入讲解

View.requestLayout()源码有干了啥?

Android 6.0 /frameworks/base/core/java/android/view/View.java

public void requestLayout() {
    
    
    if (mMeasureCache != null) mMeasureCache.clear();
    
    // AttachInfo#mViewRequestingLayout用来追踪最开始发起requestLayout的View。
    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;
    }

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
    
    
    	//这里是核心代码
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
    
    
        mAttachInfo.mViewRequestingLayout = null;
    }
}

View.requestLayout会调用 mParent.requestLayout();,也就是执行父类的requestLayout方法。

ConstraintLayout 有没有重写requestLayout方法?

有重写,但是还是调用父类ViewGroup的requestLayout,最终调用的还是View.requestLayout会。

    public void requestLayout() {
    
    
        this.markHierarchyDirty();
        super.requestLayout();
    }

ConstraintLayout的父布局是?

是DecorView。

Android6.0上,DecorView是PhoneWindow的内部类,源码路径:/frameworks/base/core/java/com.android.internal.policy.PhoneWindow.java

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    
    
	......
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    
    
    }
    ......
}

Android DecorView 一窥全貌(上)

DecorView有没有重写requestLayout方法?

没有。DecorView继承自FrameLayout,FrameLayout继承自ViewGroup, ViewGroup继承自View,所以DecorView还是调用的View的requestLayout方法。核心代码仍然是:mParent.requestLayout();

DecorView的mParent是谁?

ViewRootImpl
核心代码如下。ViewRootImpl的setView方法,通过view.assignParent(this); ,把自身传递给DecorView,作为DecorView的mParent。 这个view就是DecorView

//ViewRootImpl构造方法
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
    
    
	...
   	//持有主线程,即创建ViewRootImpl的线程
    mThread = Thread.currentThread();
    //初始化AttachInfo,内部传递持有了ViewRootImpl,后续的代码里,它将被传递给View,所以可以通过View获取到mAttachInfo,进而获取到ViewRootImpl。
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
}
ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
    
    
        synchronized (this) {
    
    
            if (mView == null) {
    
    
                //持有DecorView
                mView = view;
                ...
                //AttachInfo持有DecorView
                mAttachInfo.mRootView = view;
                //调用requestLayout
                requestLayout();
                ...
                    //通过WindowSession来完成window最终的添加,该过程mWindowSession类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,所以这其实是一次IPC的过程,远程的调用了Session的addToDisPlay方法。
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                            mTempControls);
				...
               	//将自己传递给View的mParent变量,如此调用View相关的invalidate方法,就直接调用到了ViewRootImpl
                view.assignParent(this);
                ...
        }
    }

	//View.java的assignParent方法
    void assignParent(ViewParent parent) {
    
    
        if (mParent == null) {
    
    
            mParent = parent;
        } else if (parent == null) {
    
    
            mParent = null;
        } else {
    
    
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    }

所以requestLayout()方法,最终调用了ViewRootImpl.requestLayout()方法。
DecorView的mParent具体是怎么被赋值的,过程很长,参考:关于View中mParent的来龙去脉

ViewRootImpl.requestLayout又干了啥?

    @Override
    public void requestLayout() {
    
    
        if (!mHandlingLayoutInLayoutRequest) {
    
    
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

调用了checkThread()方法

    void checkThread() {
    
    
        if (mThread != Thread.currentThread()) {
    
    
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

checkThread方法就是上面异常的抛出位置。

猜你喜欢

转载自blog.csdn.net/zhangjin1120/article/details/131074037