一:报错情况

 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方法

2:进入check for relayout方法

3:进入 request layout方法

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

然后接下来的过程就是我刚刚说的,一直向上调用到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的更多相关文章

  1. Android在子线程中更新UI(二)

    MainActivity如下: package cc.testui2; import android.os.Bundle; import android.view.View; import andro ...

  2. Android在子线程中更新UI(一)

    MainActivity如下: package cc.testui1; import android.os.Bundle; import android.os.Handler; import andr ...

  3. android 不能在子线程中更新ui的讨论和分析

    问题描写叙述 做过android开发基本都遇见过 ViewRootImpl$CalledFromWrongThreadException,上网一查,得到结果基本都是仅仅能在主线程中更改 ui.子线程要 ...

  4. 使用Handler在子线程中更新UI

    Android规定仅仅能在主线程中更新UI.假设在子线程中更新UI 的话会提演示样例如以下错误:Only the original thread that created a view hierach ...

  5. Android多线程之(一)View.post()源码分析——在子线程中更新UI

    提起View.post(),相信不少童鞋一点都不陌生,它用得最多的有两个功能,使用简便而且实用: 1)在子线程中更新UI.从子线程中切换到主线程更新UI,不需要额外new一个Handler实例来实现. ...

  6. Android开发UI之在子线程中更新UI

    转自第一行代码-Android Android是不允许在子线程中进行UI操作的.在子线程中去执行耗时操作,然后根据任务的执行结果来更新相应的UI控件,需要用到Android提供的异步消息处理机制. 代 ...

  7. C#子线程中更新ui

    本文实例总结了C#子线程更新UI控件的方法,对于桌面应用程序设计的UI界面控制来说非常有实用价值.分享给大家供大家参考之用.具体分析如下: 一般在winform C/S程序中经常会在子线程中更新控件的 ...

  8. Android 在子线程中更新UI

    今天在做练习时,在一个新开启的线程中调用“Toast.makeText(MainActivity.this, "登陆成功",Toast.LENGTH_SHORT).show();” ...

  9. 老问题:Android子线程中更新UI的3种方法

    在Android项目中经常有碰到这样的问题,在子线程中完成耗时操作之后要更新UI,下面就自己经历的一些项目总结一下更新的方法: 方法一:用Handler 1.主线程中定义Handler: Handle ...

随机推荐

  1. 菜鸡的Java笔记 java数据库编程(JDBC)

    java数据库编程(JDBC)        介绍 JDBC 的基本功能            content (内容)        现在几乎所有的项目开发过程之中都不可能离开数据库,所以在java ...

  2. java-UDP协议接收和发送数据

    UDP发送数据的步骤: A:创建发送端的Socket服务对象 B:创建数据,并把数据打包 C:通过Socket对象的发送功能发送数据包 D:释放资源 public class SendDemo {   ...

  3. [noi37]列队

    直接统计答案,令dp[i][j]表示前i个数最长的颜色各不相同后缀长度为j的方案数,如果一直令j<m,那么就相当于统计了方案数. 如何推出dp[i][j]呢?考虑i-1的最长前缀是多少:当小于j ...

  4. IPv6 寻址方式简介

     在计算机网络中,寻址模式是指在网络上托管地址的机制.IPv6 提供了多种类型的模式,可以通过这些模式对单个主机进行寻址.也可以同时对多个主机进行寻址或者寻址最近距离的主机. 单播寻址 在单播寻址方式 ...

  5. 洛谷 P3438 - [POI2006]ZAB-Frogs(乱搞/李超线段树)

    题面传送门 首先一眼二分答案,我们假设距离 \((i,j)\) 最近的 scarefrog 离它的距离为 \(mn_{i,j}\),那么当我们二分到 \(mid\) 时我们显然只能经过 \(mn_{i ...

  6. char *p、char p[]、字符串的几个题目

    总结一下遇到的关于char *p.char p[]和字符串的题目: 例一:(指针的指针) 1 void getmemory(char **p) 2 { 3 p = (char *)malloc(100 ...

  7. 20-Integer to Roman-Leetcode

    比较简单的思路:用map存放各个位的数字到罗马字符的映射 然后从个位依次遍历高位加上映射即可. Given an integer, convert it to a roman numeral. Inp ...

  8. 各个浏览器的webdriver

    Chrome 点击下载chrome的webdriver: http://chromedriver.storage.googleapis.com/index.html 不同的Chrome的版本对应的ch ...

  9. Linux驱动实践:如何编写【 GPIO 】设备的驱动程序?

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

  10. java类加载、对象创建过程

    类加载过程: 1, JVM会先去方法区中找有没有相应类的.class存在.如果有,就直接使用:如果没有,则把相关类的.class加载到方法区 2, 在.class加载到方法区时,会分为两部分加载:先加 ...