子线程更新主线程的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方法就是上面异常的抛出位置。

相关新闻

最新新闻

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现 【免费下载链接】serenity The Serenity Operating System 🐞 项目地址: https://gitcode.com/GitHub_Trending/se/serenity 导读 本文以 getopt(3) 手册 为核心&a…

2026/9/28 1:37:33
轻量服务器还是ECS?大促云服务器选购与避坑实战指南

轻量服务器还是ECS?大促云服务器选购与避坑实战指南

每年大促节点,群里永远有人在问同一个问题:“38元的轻量服务器到底怎么抢?为什么我每次点进去都是已售罄?68元直购和99元的ECS我到底选哪个?”作为一个常年帮团队和自己采购云服务器的老用户,我太清楚这种纠…

2026/9/27 19:13:42
为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南

为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南

为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南 【免费下载链接】agents Multi-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity 项目地址:…

2026/9/27 15:27:56
PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署

PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署

PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署 【免费下载链接】PaddleOCR Turn any PDF or image document into structured data for your AI. A powerful, lightweight OCR toolkit that bridges the gap between i…

2026/9/27 19:54:03
Spring源码解析:构造器注入的类型转换与候选匹配机制

Spring源码解析:构造器注入的类型转换与候选匹配机制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/28 17:20:49
openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由

openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由

openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由 【免费下载链接】openai-agents-python A lightweight, powerful framework for multi-agent workflows 项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-pyth…

2026/9/28 2:08:29

日新闻

周新闻