一句话说透Android里面的更新UI的方式

493 阅读2分钟

一句话总结:
更新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... }