「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 (下一篇)