如何在子线程中更新UI

发布时间:2022-06-28 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了如何在子线程中更新UI脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

一:报错情况

 andROId.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierArchy can touch ITs views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1606)
        at android.view.View.requestLayout(View.java:25390)
        at android.view.View.requestLayout(View.java:25390)
        at android.view.View.requestLayout(View.java:25390)
        at android.view.View.requestLayout(View.java:25390)
        at android.view.View.requestLayout(View.java:25390)
        at android.view.View.requestLayout(View.java:25390)
        at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3593)
        at android.view.View.requestLayout(View.java:25390)
        at android.widget.TextView.checkForRelayout(TextView.java:9719)
        at android.widget.TextView.setText(TextView.java:6311)

我尝试在子线程中更新UI:

binding.textView.setOnClickListener {
            thread {
                (it as TextView).text = "ldkjfla;66666sDF"
            }
        }

 

二:报错原因

首先,我们更新UI,会调用text view的request layout方法, 然后view 的request layout方法又会调用到它父view的 request layout方法:

子view request layout  ------>    父view request layout

这样一层层调用上去,因为view系统的最上层是一个叫作view root impl的view,所以最终会调用到它的request layout方法。

我们来看看它的request layout方法,然后看看有没有什么对策:

 

@override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();  和之前报错代码的最上面对应起来了
            MLayoutRequested = true;
            scheduleTraversals();
        }
    }

注意啦注意啦!!当在子view中更新UI,就会调用到view root impl的request layout方法!!然后里面会调用check thread方法来看看更新UI的线程等不等于view root impl创建时的线程!!

view root impl是在activity的onResume生命周期主线程中创建的。所以这个checkThread就不通过啦!!

 

三:解决办法

首先,上面说,更新UI时,会调用request layout方法一层层调用上去,那我们去看看这个路径,看看有没有办法斩断这个路径。

 1:进入text view的set text方法

如何在子线程中更新UI

2:进入check for relayout方法

如何在子线程中更新UI

 

3:进入 request layout方法

如何在子线程中更新UI

 

 

4:然后就进入了view的request layout方法之中

如何在子线程中更新UI

 

 

然后接下来的过程就是我刚刚说的,一直向上调用到view root impl的request layout最后报错了。注意啦!!注意啦!!在判断向不向上去传递调用request layout的时候,会看看

!mParent.isLayoutRequested()

如果我们让view root impl的LayourRequested参数为true,然后表达式为false,就不会调用到view root impl的requst layout方法,就不会check thread了。

所以我们要让这个参数为true!!!

 

四:让这个参数为true

我们可以注意到, view root impl的request layout方法中,在check thread之后,顺手就把这个参数置为true了。所以我们可以调用一次textview的request layout方法,然后调用到view root impl的方法,把它置为true,然后我们再在子线程更新UI,就不会进入view root impl的request layout方法了。

binding.textView.setOnClickListener {
            it.requestLayout()//因为在主线程,所以最后check thread没有事
            thread {
                (it as TextView).text = "ldkjfla;66666sdf"
            }
        }

 

五:LayoutRequested的意义

 我们更新UI,就会调用到view root impl的request layout方法,在check thread之后,就会把Layoutrequested置为true!!表示自己正处于被请求重新去布局的状态!!置为true,之后,下一个方法就是鼎鼎有名的

scheduleTraversals()

 

它会执行view root impl的PErformTraversal!!  对整个view tree进行从上到下的测量、布局和绘制!!在这个perform traversal结束时,会把LayoutRequested置为false。不然,下一次更新UI不就会没办法到这个方法然后失败了嘛!!所以为了性能原因,在打算开展一个perform traversal之前,会把进入标志改一下,perform traversal结束之后,又把进入标志改回来。

我们就是趁它标志位为true的时候更改UI.要是它perform traversal结束之后把标志位改了回来,,,那还是会报错。
binding.textView.setOnClickListener {
            it.requestLayout()//因为在主线程,所以最后check thread没有事
            thread {
                sleep(1000)//等了一秒, performTraversal结束之后标志位恢复,又会去到check thread了。
                (it as TextView).text = "ldkjfla;66666sdf"
            }
        }

 

 

番外:

对于text view,在checkForRelayout方法中,会看看是否改变然后决定是否向上传递request layout,所以,如果一个text view固定宽高,即使不主动request layout,在子线程中也可以修改文字不报错!!试一试!!

 

 

 

 

脚本宝典总结

以上是脚本宝典为你收集整理的如何在子线程中更新UI全部内容,希望文章能够帮你解决如何在子线程中更新UI所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。