乐于分享
好东西不私藏

Android 现代安卓线程间通信机制

Android 现代安卓线程间通信机制

我记得最初开发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.width    val 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> = _uiState    funloadData() {        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