子线程更新主线程的View抛出异常全过程
上篇文章Android子线程真的不能刷新UI吗(一复现异常复现了子线程修改UI的异常。这篇文章详细跟踪setText方法是怎么导致抛出异常的。本文目录子线程更新主线程的View会有什么后果过程具体是怎样的TextView.checkForRelayout的作用是什么View.requestLayout()源码有干了啥ConstraintLayout 有没有重写requestLayout方法ConstraintLayout的父布局是DecorView有没有重写requestLayout方法DecorView的mParent是谁ViewRootImpl.requestLayout又干了啥子线程更新主线程的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()源码publicfinalvoidsetText(CharSequencetext){//调用重载setText(text,mBufferType);}publicvoidsetText(CharSequencetext,BufferTypetype){//再次调用重载setText(text,type,true,0);if(mCharWrapper!null){mCharWrapper.mCharsnull;}}privatevoidsetText(CharSequencetext,BufferTypetype,booleannotifyBefore,intoldlen){if(mLayout!null){//在这里调用了checkForRelayoutcheckForRelayout();}...}TextView.checkForRelayout的作用是什么android 6.0 /frameworks/base/core/java/android/widget/TextView.javaprivatevoidcheckForRelayout(){if((mLayoutParams.width!LayoutParams.WRAP_CONTENT||(mMaxWidthModemMinWidthModemMaxWidthmMinWidth))(mHintnull||mHintLayout!null)(mRight-mLeft-getCompoundPaddingLeft()-getCompoundPaddingRight()0)){......}else{nullLayouts();//在这里调用了requestLayout()requestLayout();invalidate();}}调用View.requestLayout() , 关于requestLayout()可以查看View.requestLayout()使用demoView.requestLayout()源码有干了啥Android 6.0 /frameworks/base/core/java/android/view/View.javapublicvoidrequestLayout(){if(mMeasureCache!null)mMeasureCache.clear();// AttachInfo#mViewRequestingLayout用来追踪最开始发起requestLayout的View。if(mAttachInfo!nullmAttachInfo.mViewRequestingLayoutnull){// Only trigger request-during-layout logic if this is the view requesting it,// not the views in its parent hierarchyViewRootImplviewRootgetViewRootImpl();if(viewRoot!nullviewRoot.isInLayout()){if(!viewRoot.requestLayoutDuringLayout(this)){return;}}mAttachInfo.mViewRequestingLayoutthis;}mPrivateFlags|PFLAG_FORCE_LAYOUT;mPrivateFlags|PFLAG_INVALIDATED;if(mParent!null!mParent.isLayoutRequested()){//这里是核心代码mParent.requestLayout();}if(mAttachInfo!nullmAttachInfo.mViewRequestingLayoutthis){mAttachInfo.mViewRequestingLayoutnull;}}View.requestLayout会调用mParent.requestLayout();也就是执行父类的requestLayout方法。ConstraintLayout 有没有重写requestLayout方法有重写但是还是调用父类ViewGroup的requestLayout最终调用的还是View.requestLayout会。publicvoidrequestLayout(){this.markHierarchyDirty();super.requestLayout();}ConstraintLayout的父布局是是DecorView。Android6.0上DecorView是PhoneWindow的内部类源码路径/frameworks/base/core/java/com.android.internal.policy.PhoneWindow.javapublicclassPhoneWindowextendsWindowimplementsMenuBuilder.Callback{......privatefinalclassDecorViewextendsFrameLayoutimplementsRootViewSurfaceTaker{}......}Android DecorView 一窥全貌上DecorView有没有重写requestLayout方法没有。DecorView继承自FrameLayoutFrameLayout继承自ViewGroup, ViewGroup继承自View所以DecorView还是调用的View的requestLayout方法。核心代码仍然是mParent.requestLayout();DecorView的mParent是谁ViewRootImpl核心代码如下。ViewRootImpl的setView方法通过view.assignParent(this);把自身传递给DecorView作为DecorView的mParent。 这个view就是DecorView//ViewRootImpl构造方法publicViewRootImpl(UiContextContextcontext,Displaydisplay,IWindowSessionsession,booleanuseSfChoreographer){...//持有主线程即创建ViewRootImpl的线程mThreadThread.currentThread();//初始化AttachInfo内部传递持有了ViewRootImpl后续的代码里它将被传递给View所以可以通过View获取到mAttachInfo进而获取到ViewRootImpl。mAttachInfonewView.AttachInfo(mWindowSession,mWindow,display,this,mHandler,this,context);}ViewRootImplpublicvoidsetView(Viewview,WindowManager.LayoutParamsattrs,ViewpanelParentView,intuserId){synchronized(this){if(mViewnull){//持有DecorViewmViewview;...//AttachInfo持有DecorViewmAttachInfo.mRootViewview;//调用requestLayoutrequestLayout();...//通过WindowSession来完成window最终的添加该过程mWindowSession类型是IWindowSession它是一个Binder对象真正的实现类是Session所以这其实是一次IPC的过程远程的调用了Session的addToDisPlay方法。resmWindowSession.addToDisplayAsUser(mWindow,mWindowAttributes,getHostVisibility(),mDisplay.getDisplayId(),userId,mInsetsController.getRequestedVisibility(),inputChannel,mTempInsets,mTempControls);...//将自己传递给View的mParent变量如此调用View相关的invalidate方法就直接调用到了ViewRootImplview.assignParent(this);...}}//View.java的assignParent方法voidassignParent(ViewParentparent){if(mParentnull){mParentparent;}elseif(parentnull){mParentnull;}else{thrownewRuntimeException(view this being added, but it already has a parent);}}所以requestLayout()方法最终调用了ViewRootImpl.requestLayout方法。DecorView的mParent具体是怎么被赋值的过程很长参考关于View中mParent的来龙去脉ViewRootImpl.requestLayout又干了啥OverridepublicvoidrequestLayout(){if(!mHandlingLayoutInLayoutRequest){checkThread();mLayoutRequestedtrue;scheduleTraversals();}}调用了checkThread()方法voidcheckThread(){if(mThread!Thread.currentThread()){thrownewCalledFromWrongThreadException(Only the original thread that created a view hierarchy can touch its views.);}}checkThread方法就是上面异常的抛出位置。

相关新闻

最新新闻

日新闻

周新闻

月新闻