一句话总结:
更新UI就像给主线程发微信——子线程不能直接改UI,得找“中间人”传话,否则App分分钟崩溃给你看!
一、主线程(UI线程)的规矩:
- 规矩1: 只有主线程能摸UI(TextView、Button等),其他线程乱摸 → 直接崩溃!
- 规矩2: 主线程不能干重活(网络请求、数据库操作)→ 否则卡成ANR弹窗!
类比: 主线程是单行道交警,子线程是送货小哥。小哥不能自己摆摊(改UI),必须把货交给交警代发!
二、经典传话方式(保命指南)
方式1:runOnUiThread(官方快递,一键直达)
// 在Activity/Fragment中使用(最简单!)
thread {
// 子线程干活
val data = fetchDataFromServer()
runOnUiThread { // 切回主线程更新UI
textView.text = data
}
}
优点: 代码少,适合Activity环境。
缺点: 只能在Activity里用,Fragment得用requireActivity().runOnUiThread。
方式2:View.post(有View就能用,灵活投送)
// 任意地方只要有View实例(比如自定义View)
thread {
val result = doHeavyWork()
textView.post { // 把任务扔给textView的主线程
textView.text = result
}
}
优点: 不用依赖Activity,有View就能发!
方式3:Handler(老司机专用,手动挡)
// 创建Handler(记得用主线程Looper!)
val handler = Handler(Looper.getMainLooper())
thread {
val data = loadData()
handler.post { // 发送任务到主线程队列
textView.text = data
}
}
坑点: 忘记用Looper.getMainLooper() → 消息发到子线程 → UI照样崩!
方式4:LiveData(MVVM神器,自带生命周期检测)
// ViewModel中
val data = MutableLiveData<String>()
// 子线程发数据
thread {
val result = fetchData()
data.postValue(result) // 自动切主线程!
}
// Activity/Fragment中观察
data.observe(this) { value ->
textView.text = value // 主线程安全更新
}
优点: 不用手动切换线程,避免内存泄漏!
方式5:协程(Kotlin亲儿子,优雅之王)
// 在ViewModel或LifecycleScope中
viewModelScope.launch(Dispatchers.IO) {
val data = api.getData() // 子线程执行
withContext(Dispatchers.Main) { // 切回主线程
textView.text = data
}
}
优点: 代码顺序执行,不用回调地狱!
方式6:RxJava(响应式大佬,花式切换)
Observable.fromCallable { // 子线程执行
fetchDataFromDB()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { data ->
textView.text = data // 主线程更新
}
适用场景: 项目已用RxJava,装逼必备!
三、千万不能这样作死!
案例1:子线程直接改UI
thread {
textView.text = "Hi" // 直接崩溃!Crash: Only the original thread can touch its views
}
案例2:主线程干重活
button.setOnClickListener {
Thread.sleep(5000) // 主线程睡觉5秒 → ANR弹窗!
}
四、怎么选?看场景!
| 场景 | 推荐方式 | 优点 |
|---|---|---|
| 简单回调 | runOnUiThread/View.post | 代码少,随手用 |
| MVVM架构 | LiveData | 生命周期安全 |
| 协程项目 | withContext(Dispatchers.Main) | 顺序执行,优雅 |
| 复杂数据流 | RxJava | 操作符强大 |
| 底层控制 | Handler | 灵活,可定时发送 |
终极口诀:
“子线程不能摸UI,想更新得找中间人!
LiveData协程是首选,Handler老将也能战!”
(附赠一张灵魂示意图👇)
子线程 → [中间人] → 主线程 → 更新UI
中间人 = { Handler、runOnUiThread、LiveData、RxJava... }