首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >为什么有时候在子线程更新UI没报错?

为什么有时候在子线程更新UI没报错?

作者头像
程思扬
修改于 2022-01-14 02:07:59
修改于 2022-01-14 02:07:59
72620
代码可运行
举报
文章被收录于专栏:程思阳的专栏程思阳的专栏
运行总次数:0
代码可运行

抓住十一月的尾巴,分享一首童年回忆: 🎶brave heart

看到这个标题,好多人第一时间想到的是什么? 感兴趣的不妨跟着下面的代码看看会发生什么? 首先我在 onCreate 方法里调用 setText() 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mContext = this
        Log.e(TAG, "onCreate")
        Thread {
            val simpleDateFormat = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss") // HH:mm:ss
            //获取当前时间
            val date = Date(System.currentTimeMillis())
            // 更新TextView文本
            tv_title.text="Date获取当前日期时间" + simpleDateFormat.format(date))
        }.start()

    }

这个时候允许呢,会发现,哎?为什么正常呢,不应该报错吗? 但是呢, 当我改成了这样以后再次运行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        Thread {
            try {
                Thread.sleep(2000)
                val simpleDateFormat = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss") // HH:mm:ss
                //获取当前时间
                val date = Date(System.currentTimeMillis())
                // 更新TextView文本内容
                tv_title.text = "Date获取当前日期时间" + simpleDateFormat.format(date)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
        }.start()

结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
       at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6557)
       at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:943)
       at android.view.ViewGroup.invalidateChild(ViewGroup.java:5081)
       at android.view.View.invalidateInternal(View.java:12719)
       at android.view.View.invalidate(View.java:12683)
       at android.view.View.invalidate(View.java:12667)
       at android.widget.TextView.checkForRelayout(TextView.java:7167)
       at android.widget.TextView.setText(TextView.java:4347)
       at android.widget.TextView.setText(TextView.java:4204)
       at android.widget.TextView.setText(TextView.java:4179)

这应该就是大家熟悉的报错了吧,不允许在非UI线程中更新UI线程 既然报这个错了,那就跟进去,看看 ViewRootImpl.java 为什么报这个错,之前分享过看源码的方式。 点我看源码 既然报错已经告诉我们在哪一行了,那我们就点进去看看,可以很容易的找到 在这里要说明一下,在Android2.2以后是用ViewRootImpl来代替ViewRoot的,用来连接WindowManager和DecorView,而且View的绘制也是通过ViewRootImpl来完成的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
6554  void checkThread() {
6555        if (mThread != Thread.currentThread()) {
6556            throw new CalledFromWrongThreadException(
6557                    "Only the original thread that created a view hierarchy can touch its views.");
6558        }
6559    }

当Activity对象被创建完毕后,将DecorView添加到Window中,同时会创建ViewRootImpl对象,在源码中可以看到 mThread 是在ViewRootImpl 的构造方法里这样初始化的。然后再把他设为主线程。

那现在捋一下,从上面的错误栈里,可以看到调用的流程是:

at android.widget.TextView.setText(TextView.java:4347) at android.widget.TextView.checkForRelayout(TextView.java:7167) at android.view.View.invalidate(View.java:12667) at android.view.View.invalidateInternal(View.java:12719) atandroid.view.ViewGroup.invalidateChild(ViewGroup.java:5081) atandroid.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:943) at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6557)

所以现在聪明的同事是不是知道了,要去看看 ViewRootImpl 这个是在哪里被初始化的?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
3126    final void handleResumeActivity(IBinder token,
3127            boolean clearHide, boolean isForward, boolean reallyResume) {
                                    ...
3158            if (r.window == null && !a.mFinished && willBeVisible) {
3159                r.window = r.activity.getWindow();
3160                View decor = r.window.getDecorView();
3161                decor.setVisibility(View.INVISIBLE);
3162                ViewManager wm = a.getWindowManager();
3163                WindowManager.LayoutParams l = r.window.getAttributes();
3164                a.mDecor = decor;
3165                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
3166                l.softInputMode |= forwardBit;
3167                if (a.mVisibleFromClient) {
3168                    a.mWindowAdded = true;
3169                    wm.addView(decor, l);
3170                }
3171
3172           ...
3247    }

最后的 wm.addView(decor, l) 就是我们要找的答案,这个时候看一下windowManager.addView(decorView)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public void addView(View view, ViewGroup.LayoutParams params,
232            Display display, Window parentWindow) {
233        if (view == null) {
234            throw new IllegalArgumentException("view must not be null");
235        }
236        if (display == null) {
237            throw new IllegalArgumentException("display must not be null");
238        }
239        if (!(params instanceof WindowManager.LayoutParams)) {
240            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
241        }
242
257        ViewRootImpl root;
258        View panelParentView = null;

                 ...
303            mViews.add(view);
304            mRoots.add(root);
305            mParams.add(wparams);
306        }
307
308        // do this last because it fires off messages to start doing things
309        try {
310            root.setView(view, wparams, panelParentView);
311        } catch (RuntimeException e) {
312            // BadTokenException or InvalidDisplayException, clean up.
313            synchronized (mLock) {
314                final int index = findViewLocked(view, false);
315                if (index >= 0) {
316                    removeViewLocked(index, true);
317                }
318            }
319            throw e;
320        }
321    }

看到这里,是不是有种豁然开朗的感觉,因为已经找到了答案,答案就是跟 ViewRootImpl 的初始化有关,因为我之前的代码是在 onCreate() 的时候此时去设置textview,此时呢 View 还没被绘制出来,ViewRootImpl 还未创建,它的创建是在 handleResumeActivity() 的调用到 windowManager.addView(decorView) 时候。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/11/30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
2 条评论
热度
最新
666
666
回复回复点赞举报
666
666
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
可能是全网最简单透彻的安卓子线程更新 UI 解析
相信下面的代码大家看过很多遍了,在 onCreate() 生命周期里开启一个线程来更新 UI ,居然没有闪退和异常( 在大概率情况下是没有问题的 )
萬物並作吾以觀復
2019/05/09
1.2K0
子线程 真的不能更新UI ?
一般情况,我们在子线程直接操作UI,没有用handler切到主线程,就会报这个错。
胡飞洋
2020/07/23
1.4K0
子线程 真的不能更新UI ?
面试官:View.post() 为什么能够获取到 View 的宽高?
说一些题外话,Android 面试进阶指南 其实是我在小专栏维护的一个付费专栏,且已经有部分付费用户。本文是第九篇文章了,为了维护付费用户的权益,没有办法把所有文章都同步到公众号。如果你对这个专栏感兴趣,不妨点击文末 阅读原文 了解专栏详情。
路遥TM
2021/08/31
1.5K0
长谈:关于 View Measure 测量机制,让我一次把话说完
首先声明,这一篇篇幅很长很长很长的文章。目的就是为了把 Android 中关于 View 测量的机制一次性说清楚。算是自己对自己较真。写的时候花了好几天,几次想放弃,想放弃的原因不是我自己没有弄清楚,而是觉得自己叙事脉络已经紊乱了,感觉无法让读者整明白,怕把读者带到沟里面去,怕自己让人觉得罗嗦废话。但最后,我决定还是坚持下去,因为在反复纠结 –> 不甘 –> 探索 –> 论证 –> 质疑的过程循环中,我完成了对自己的升华,弄明白长久以来的一些困惑。所以,此文最大的目的是给自己作为一些学习记录,如果有幸帮助你解决一些困惑,那么我心宽慰。如果有错的地方,也欢迎指出批评。
Frank909
2019/01/14
7980
Android采坑之路(一):怀疑人生,主线程修改UI也会崩溃?
某天早晨,吃完早餐,坐回工位,打开电脑,开启chrome,进入友盟页面,发现了一个崩溃信息:
Android技术干货分享
2020/11/18
5.2K0
Android采坑之路(一):怀疑人生,主线程修改UI也会崩溃?
Android模拟面试,解锁大厂——从Activity创建到View呈现中间发生了什么?
前段时间公司招人,作为面试官,我经常让面试者简述View的绘制流程。他们基本都能讲明白View的测量(measure)、布局(layout)、绘制(draw)等过程。还有少数人会提到DecorView和ViewRootImp的作用。但是,当我继续追问关于Window的内容时,几乎没有人回答上来。而本章将会带你深入理解Window、DecorView、ViewRootImp。除此之外,你还能在本章找到以下问题的答案:
Android技术干货分享
2020/09/22
8750
Android模拟面试,解锁大厂——从Activity创建到View呈现中间发生了什么?
Android 子线程 UI 操作真的不可以?
某 SDK 有 PopupWindow 弹窗及动效,由于业务场景要求,对于 App 而言,SDK 的弹窗弹出时机具有随机性。
2020labs小助手
2022/05/24
1.2K0
Android | 理解 ViewRootImpl
ViewRootImpl 是 View 的最高层级,是所有 View 的根。ViewRootImpl 实现了 View 和 WindowManager 之间所需要的协议。ViewRootImpl 的创建过程是从 WindowManagerImpl 中开始的。View 的测量,布局,绘制以及上屏,都是从 ViewRootImpl 中开始的。
345
2022/03/25
1.3K0
Android | 理解 ViewRootImpl
面试官问我:Andriod为什么不能在子线程更新UI?
看完《你为什么在现在的公司不离职?》,很多同学踏上了面试之路,作为颜值担当的天才少年_也开始了面试之路。
天才少年
2020/06/27
9650
面试官问我:Andriod为什么不能在子线程更新UI?
Unable to add window --token is not valid
Build: ***:4.0.4/IMM76D/1348165925:eng/test-keys
再见孙悟空_
2023/02/10
5340
《Activity显示界面历险记》
后面也会陆续说到一些面试的常考知识点,比如Activity、Handler、RecyclerView等等,希望各位继续支持,❤️。
码上积木
2021/03/10
5550
Activity启动时View首次绘制的源码深度解析:从handleResumeActivity()到performTraversals()
这是整个绘制流程的起点,位于frameworks/base/core/java/android/app/ActivityThread.java:
李林LiLin
2025/07/18
1310
ViewRootImpl的独白,我不是一个View(布局篇)
前一段时间写过两篇关于View的文章 Activity中的Window的setContentView 和 遇见LayoutInflater&Factory 。分析了Activity设置页面布局到页面View元素进行布局到底经历了一个怎么样的过程?
静默加载
2020/05/29
8500
面试官:如何监测应用的 FPS ?
即使你不知道 FPS,但你一定听说过这么一句话,在 Android 中,每一帧的绘制时间不要超过 16.67ms。那么,这个 16.67ms 是怎么来的呢?就是由 FPS 决定的。
音视频开发进阶
2020/11/10
1.6K0
面试官:如何监测应用的 FPS ?
线程与更新UI,细谈原理
相信不少读者都阅读过相类似的文章了,但是我还是想完整的把这之间的关系梳理清楚,细节聊好,希望你也能从中学到一些。
码上积木
2020/11/24
9770
必要掌握!Window、WindowManager !
Window是View的管理者,当我们说创建Window时,一方面指实例化这个管理者,一方面指 用WindowManager.addView()添加view,以view的形式来呈现Window这个概念。
胡飞洋
2020/07/23
1.7K0
必要掌握!Window、WindowManager !
Android | 理解 Window 和 WindowManager
Window 是一个窗口的概念,是所有视图的载体,不管是 Activity,Dialog,还是 Toast,他们的视图都是附加在 Window 上面的。例如在桌面显示一个悬浮窗,就需要用到 Window 来实现。WindowManager 是访问 Window 的入口。
345
2022/03/25
1.1K0
Android | 理解 Window 和 WindowManager
竟如此简单,一文看懂DecorView的一生
DecorView是Android应用程序中所有视图的根视图。它是框架用来管理和显示应用程序界面的核心组件之一。理解DecorView的创建流程对于理解Android视图系统的运作方式至关重要。
Rouse
2024/04/11
8240
竟如此简单,一文看懂DecorView的一生
粗谈绘制任务和绘制流程
今天是2028年4月26日,天气晴,我请了一天假在家陪女儿。 正在陪女儿画画的我,被女儿问到: ?:“爸爸,妈妈说你的工作是可以把我们想到的东西变到手机上,是这样吗?” ?:“对呀,厉害吧~” ?:“
码上积木
2021/04/30
8010
Window十二问(快扶我起来,我还能问)
窗口。你可以理解为手机上的整个画面,所有的视图都是通过Window呈现的,比如Activity、dialog都是附加在Window上的。Window类的唯一实现是PhoneWindow,这个名字就更加好记了吧,手机窗口呗。
码上积木
2021/02/12
6410
Window十二问(快扶我起来,我还能问)
推荐阅读
相关推荐
可能是全网最简单透彻的安卓子线程更新 UI 解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验