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

浙公網安備 33010602011771號