我记得最初开发Android的时候,我们会对安卓要切换主子线程感到疑惑,感觉和后端的开发方式差别太大,后端只需要关注业务就行不需要关注什么线程。而Android跳跃感太强,所以很多人在第一步就开始退却。
在Kotlin还没成熟之前,线程间通信的手段很少,我刚开一般就只用Handler,因为我只对Handler通信熟悉。
虽然只用Handler也可以实现我们的需求,但是作为一个成熟的Android开发我们不能只局限于一种,要多了解其他,以便可以在各种场景选择最适合的通信方式。

一、Handler通信
这种通信机制应该是每一位Android开发最开始接触的一种,相信大家都对它挺熟练的。
Handler通信的使用方法和原理都是面试常问的问题,这一点如果回答得不好,那这次面试大概率会泡汤。Handler机制大家可以看看我这篇,对大家的面试会有所帮助。Android 从 ActivityThread 开始,彻底搞懂 Handler 机制
首先我们都知道Handler是Android最经典的通信方式,通常用于子线程向主现场发送任务。使用方式如下,我们首先要把这个Handler设置为类变量,例如:
private val mainHandler = Handler(Looper.getMainLooper())上面的代码表示这个handler已经和主线程绑定了,子线程可以随时用它mainHandler去更新UI。比如:
Thread {val data = loadData()mainHandler.post {textView.text = data}}.start()
以前大家都喜欢使用这种,大概率就是使用简单并且还是Android原生机制。它的底层是使用Looper去执行任务,使用MessageQueue去存储任务。
它可以支持这几种方式:
(1)消息立即执行,post(Runnable)将任务加入消息队列。
(2)消息延迟执行,postDelayed(Runnable,delayMillis)或postAtTime(...)可以让Looper延迟执行。
(3)消息发送等,这种也是很常见的自定义通信,发送消息sendMessage(Message),由handleMessage(Message)接收处理。
具备这些能力的企业都是会抢着要的,所以问题不是岗位没了,而是岗位还在,但是不是企业想要的人。

二、runOnUiThread
runOnUiThread是Activity提供的一个便捷方法,这个方法的意思是在UI线程上运行,意义是方便某代码在主现场运行。示例方法如下:
Thread {// 子线程执行val nameStr = getUserName()runOnUiThread {// 主线程执行userNameTv.text = nameStr}}.start()
这种方式非常适合异步返回数据时更新UI的场景。其实它的底层没那么高级很简单的,使用就是我们上面所说的Handler机制,代码如下:
publicfinalvoidrunOnUiThread(Runnable action){if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();}}
上面的代码有两条路径,第一条假如是在子线程运行那么就通过一个和主现场绑定的handler发送消息,第二条假如已经是主线程那么就直接运行,记住这里没有新建线程。

三、View.post
上面的那个方法属于Activity的方法,而这个是属于View的方法View.post。
底层还是通过主线程的mHandler去发送消息。
唯一的区别是runOnUiThread如果已经在主现场那就直接执行,而View.pos不管是哪个线程都是通过mHandler去执行。
public boolean post(Runnable action) {if (mAttachInfo != null) {return mAttachInfo.mHandler.post(action);}getRunQueue().post(action);return true;}
mAttachInfo这个参数表示view是否已经连接窗口,为空就表示还没有连接窗口,然后就保存到内部的队列,等连接后再发送。
view.post {val width = view.widthval height = view.height}
常用场景是在子线程更新某组件的UI。

四、Kotlin协程
协程是现代Android项目中最常用的异步处理方式,这是一种更简洁的方式,可以执行耗时任务,同时不阻塞任务并且逻辑看起来是同步的,让android学习成本大大降低。
lifecycleScope.launch {val data = withContext(Dispatchers.IO) {loadData()}textView.text = data}
例如上面的代码就是同步的一种方式,以往在没有kotlin协程的时候,我们要通过Handler去更新UI,后续的业务更是一层套着一层,就像俄罗斯套娃一样,使得整个业务理解起来非常麻烦。
上面的处理使得业务“像在同一线程执行,但是又不阻塞当前线程”。如果大家要想更加理解Kotlin请看看我这篇文章,相信对大家会有所帮助。Android 【Kotlin 最终章】重量级-协程和inline
Kotlin协程常用调度器有这三个:
(1)Dispatchers.Main,主线程,负债UI的更新
(2)Dispatchers.IO,负责网络、文件、数据库的处理,负责IO密集型处理。
(3)Dispatchers.Default,负责计算类的耗时,也就是计算密集型处理。
使用Kotlin协程的特点如下:
(1)线程切换简单
(2)支持取消和异常处理
(3)能与Activity、Fragment、ViewModel生命周期绑定
(4)代码以同步的方式表示,开发者更容易阅读

五、Flow、StateFlow
Flow通常用于线程和协程之间传递连续数据,StateFlow常用于ViewModel向UI传递页面状态。用法如下所示:
class UserViewModel : ViewModel() {private val _uiState = MutableStateFlow("加载中")val uiState: StateFlow<String> = _uiStatefunloadData() {viewModelScope.launch {val data = withContext(Dispatchers.IO) {repository.loadData()}_uiState.value = data}}}
内部就使用可变的MutableStateFlow,而对外提供的是不可变的StateFlow。
它的特点如下:
(1)可以连续发送多个数据
(2)StateFlow始终保存最新状态
(3)支持过滤、转换和组合数据
(4)与协程和Jetpack生命周期组件配合良好

六、LiveData
LiveData是传统Android MVVM项目中常见的线程通信方式,通常是用来更新UI,它的原理是通过观察者模式实现的。用法如下:
userModel.userName.observe(viewLifecycleOwner) { name ->usernameTv.text = name}
class UserViewModel : ViewModel() {val userName = MutableLiveData<String>()funloadData() {Thread {val name = loadUserName()userName.postValue(name)}.start()}}
它的特点是:
(1)感知Activity和Fragment生命周期。
(2)页面销毁后自动移除观察关系
(3)对XML、Data Binding项目友好
(4)数据处理能力不如Flow灵活

七、总结
实际可以重点写成三代方案:
(1)传统方案:Handler、runOnUiThread、View.post
(2)Jetpack MVVM方案:LiveData
(3)现代推荐方案:Coroutine、Flow、StateFlow
夜雨聆风