The child thread updates the View of the main thread and throws an exception during the whole process

Previous article: Can Android sub-threads really refresh the UI? (1) Reproduce the exception , which reproduces the exception that the sub-thread modifies the UI. This article traces in detail how the setText method causes an exception to be thrown.

What are the consequences of the child thread updating the View of the main thread?

will throw an exception

What is the process like?

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() source code:

    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();
        }

		...
    }

What does TextView.checkForRelayout do?

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();
        }
    }

In-depth explanation of calling View.requestLayout() Android sub-thread and updating UI

What did the source code of View.requestLayout() do?

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 will call mParent.requestLayout();, that is, execute the requestLayout method of the parent class.

Does ConstraintLayout override the requestLayout method?

There is rewriting, but the requestLayout of the parent class ViewGroup is still called, and View.requestLayout will be called in the end.

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

What is the parent layout of ConstraintLayout?

is DecorView.

On Android6.0, DecorView is an internal class of PhoneWindow, source path: /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 has a glimpse of the whole picture (on)

Does DecorView override the requestLayout method?

No. DecorView inherits from FrameLayout, FrameLayout inherits from ViewGroup, and ViewGroup inherits from View, so DecorView still calls the requestLayout method of View. The core code is still:mParent.requestLayout();

Who is DecorView's mParent?

ViewRootImpl
The core code is as follows. The setView method of ViewRootImpl view.assignParent(this);passes itself to DecorView as the mParent of DecorView. This view is 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");
        }
    }

So the requestLayout() method finally calls the ViewRootImpl.requestLayout() method.
How the mParent of DecorView is assigned, the process is very long, refer to: About the ins and outs of mParent in View

What does ViewRootImpl.requestLayout do?

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

The checkThread() method is called

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

The checkThread method is where the above exception is thrown.

Guess you like

Origin blog.csdn.net/zhangjin1120/article/details/131074037
Recommended