「Android面试」Android 子线程为什么直接更新UI?

  本文将从子线程不能更新UI的直接原因、根本原因、Android如何做到限制以及子线程该如何正确更新UI四个方向回答问题。

【直接原因】在子线程中更新UI会怎样?

程序会出现以下错误: Only the original thread that created a view hierarchy can touch its views.

 

【根本原因】看到问题的本质(根本原因)

多线程操作UI是不安全的,那么为什么不安全?..

假设子线程能够更新UI,现在有个TextView显示数字1,我们现在需要对这个textview的现在的数字加1显示2,即 tv.text = tv.text +1 

 tv.text =  (tv.text.toString().toIntOrNull()?:0 + 1).toString()

我们把这个模型简化,这个计算类似于 x = x +1 的操作

x = x +1

那么证明 x = x +1不是线程安全的也就证明多线程操作  tv.text = tv.text +1 也不是安全的

🌰我们使用两个线程分别去将x加10w,打印出x的值看看到底是多少?

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        val tv = findViewById<TextView>(R.id.textview)

        for (i in 1..100000) {
            xadd()
        }
        for (i in 1..100000) {
            xadd()
        }
        Log.i("WQY0", "x = $x")
        Thread {
            for (i in 1..100000) {
                xadd()
            }
            Log.i("WQY1", "x = $x")
        }.start()

        Thread {
            for (i in 1..100000) {
                xadd()
            }
            Log.i("WQY2", "x = $x")
        }.start()


    }
    var x = 0

    fun xadd(){
        ++x
    }
}

 

 

理论上WQY0打印结果是20w,WQY1和WQY2其中有一个x打印结果是40w,但实际结果就是WQY1和WQY2都不是40w。所以多线程操作同一个资源是不安全的,对于UI操作也是同样的,多线程操作UI时不安全的,这是根本原因。

 

用锁解决线程不安全的问题

锁能保证同一时间只能有一个线程访问,具体如下

    @Synchronized
    fun xadd() {
        ++x
    }

另一种写法

    fun xadd() {
        synchronized(this) {
            ++x
        }
    }

 原来的程序执行结果

 

 

那么Android为什么不使用锁的方式解决UI资源安全问题?

锁会影响代码执行的效率。UI操作变慢的话界面卡顿,影响用户体验

 

【如何限制】Android如何实现控制子线程直接更新UI?

 由于报错是在ViewRootImpl,是View的requestLayout调用到ViewRootImpl的requestLayout,所以我们需要知道ViewRootImpl是什么,View(我们更新的TextView)和ViewRootImpl之间的关系又是什么?ViewRootImpl是在什么时候创建,以及什么时候调用ViewRootImpl.requestLayout方法?

view setText的时候触发了view的重绘,向下调用到父类view.requestLayout,最终执行到ViewRootImpl的requestLayout方法,所以在两种情况下可以在子线程中更新UI,第一次ViewRootImpl创建之前,比如onCreate里面无延迟放在子线程中,第二种就是不会造成view的requestLayout,比如给view设置固定的尺寸。

 了解View的绘制流程,View的控件树结构(下一篇)

 

【如何更新】子线程想要更新UI怎么办?

跨线程通信的方式

1、使用Handler

2、Rxjava

3、广播、通知等

子线程通过Handler切换到主线程更新UI (下一篇)

 

posted @ 2024-08-08 19:14  茄子鱼  阅读(22)  评论(1编辑  收藏  举报