关于kotlin中的flow(一)

5,202 阅读3分钟

概念

FLow: 异步流。概念上讲依然是响应式流。这和 Rxjava 很像。熟悉 Rxjava 的开发者可以很快适应 Flow。Flow 提供了很多丰富的操作符,例如 mapflitercount 等等,相比 Rxjava ,Flow 的使用和线程切换更为简单。以下是一个简单的例子。

过渡操作符: 过渡操作符应用于上游流,并返回下游流。这些操作符也是冷操作符,也就是说如果没有 '被订阅'就不会执行。onStart/catch/onCompletion/map/filter 等都是过渡操作符。

末端流操作符:在流上用于启动流收集的挂起函数。 collect 是最基础的末端操作符。first/toList/reduce 等都是末端操作符。类型 Rxjava 的订阅。这里可以理解是收集最终的流结果。

冷流:Flow 是一种类似于序列的冷流 — 上游的代码直到流被收集的时候才运行,类似 RxJava 上游流被订阅了才会执行。

example:

  private fun getData(): String {
        Thread.sleep(3000)
        return "Flow test"
    }

    @Test
    fun testFlowNormal() {
        GlobalScope.launch {
            flow {
                emit(getData())
            }.flowOn(Dispatchers.IO) //切换线程
                .onStart {
                    println("onStart")
                }.catch {
                    println("catch:${it.message}")//有异常会进入此方法
                }.onCompletion {
                    println("oniComplete:${it?.message}")//无论是否有异常都会执行
                }.collect {//收集流
                    println("result = $it")
                }
        }
        Thread.sleep(6000)
    }

输出结果:

onStart
result = Flow test
oniComplete:null

注: catchonCompletion 的执行是有顺序的,顺序为调用先后的顺序。

关于冷流

冷流的意思是只有当collect等末端操作符调用,整个flow才会启动调用,包括中途的过渡操作符,也就是说,如果将过渡操作符生成的flow独立出来,但是末端操作符最后作用在其中一个flow中,其他的flow对象并不能访问到过渡操作符。

和LiveData的区别

flow是kotlin中类似rxJava中flowable的响应流。同样是观察者模式,和livedata的区别在于

1、livedata是生命周期感知的,在整个mvvm架构中适合使用在view层和viewmodel层之间交互,而flow是没有生命周期感知,适合用在model层和viewmodel之间

2、livedata无法处理背压问题,只能显示最新数据,flow和flowable类似可以处理这类问题

3、livedata都放在主线程处理数据,对于线程控制不太理想,flow可以结合协程实现线程切换

retrofit和room使用flow

flow是协程的扩展,如果要使用,需要库本身支持协程,room>2.1 retrofit>2.6.0版本都可以使用。

room:在room中可以直接使用suspend返回查询对象或者不加suspend使用Flow<>返回封装的flow对象

retrofit:使用suspend关键字。

viewmodel中使用flow

讲room或者retrofit中用suspend操作符修饰的操作放到flow{}中处理,最后用emit()将数据发送(flow是冷流,也就是在调用之前):

flow {
    // 进行网络或者数据库操作
    emit(model)
}.flowOn(Dispatchers.IO) // 通过 flowOn 切换到 IO 线程

上面是一般发送flow的方式,在flow{}中也可以是普通的耗时操作,flow在viewmodel和view中,一般结合liveData实现生命周期感知

flow和liveData组合使用通知界面变化

在lifecycle 2.2.0之前的版本,官方推荐使用2个live实现界面的通知:

// 私有的 MutableLiveData 可变的,对内访问
private val _pokemon = MutableLiveData<PokemonInfoModel>()

// 对外暴露不可变的 LiveData,只能查询
val pokemon: LiveData<PokemonInfoModel> = _pokemon

viewModelScope.launch {
    polemonRepository.featchPokemonInfo(name)
        .onStart {
            // 在调用 flow 请求数据之前,做一些准备工作,例如显示正在加载数据的进度条
        }
        .catch {
            // 捕获上游出现的异常
        }
        .onCompletion {
            // 请求完成
        }
        .collectLatest {
            // 将数据提供给 Activity 或者 Fragment
            _pokemon.postValue(it)
        }
}

lifecycle 2.2.0以后的版本,LiveData有协程的构造方法:

fun fectchPokemonInfo(name: String) = liveData<PokemonInfoModel> {
    polemonRepository.featchPokemonInfo(name)//这是ritrofit使用suspend返回的
        .onStart { // 在调用 flow 请求数据之前,做一些准备工作,例如显示正在加载数据的进度条 }
        .catch { // 捕获上游出现的异常 }
        .onCompletion { // 请求完成 }
        .collectLatest {
            // 更新 LiveData 的数据
            emit(it)
        }
}

还可以使用asLiveData直接将一个flow转化成一个不可变的liveData

suspend fun fectchPokemonInfo3(name: String) =
    polemonRepository.featchPokemonInfo(name)//retrofit中使用suspend修饰
        .onStart {
            // 在调用 flow 请求数据之前,做一些准备工作,例如显示正在加载数据的按钮
        }
        .catch {
            // 捕获上游出现的异常
        }
        .onCompletion {
            // 请求完成
        }.asLiveData()

下一章主要结合源码讲解一下StateFlow和SharedFlow以及热流的一些知识:

下一章关于kotlin中的flow(二)