Kotlin Flow 基础概念

文章目录

    本以为我的 Android 开发技术已经天下无敌了😅,没想到连 Flow / Channel 我都第一次见。。。不得不说 Android 的新概念层出不穷。

    周末看了一堆文档,有了一个大概的了解。我发现微信公众号的文章质量还真是高,不少高手都做了详细的对比说明。
    但是最终我一个也没有关注,从这些人的近期推文看都堕落了,不是贩卖焦炉,就是无脑卖其他骗子的课。扯远了,开始正题。

    Flow 为何而生

    一个挂起函数 (suspending function) 可以异步地返回一个值,但是没法多次返回。于是 Flow 就诞生了。
    这也是为什么 Flow 获取 select 结果不需要加上 suspend 关键词,因为本身就不是挂起函数。

    Flow 是 Kotlin 语言层的实现,所以解决了 LiveData 无法跨平台的问题。
    同时 Flow 也解决了 LiveData 只能在 UI 主线程更新值的缺点。

    cold / hot stream

    Flow 是基于 Kotlin Coroutines 实现的 cold stream,即冷流。

    注意:虽然 flow 是 code stream,但是新引入的 flow 子类, StateFlow 和 SharedFlow 是 hot stream。

    • hot stream: 热流,即便没有消费端订阅数据,生产端也会一直 push 数据。而 channel 则是 hot stream。
    • cold stream: 热流,只有当消费端开始 collect 数据的时候,生产端才会 push 数据。

    flow 的几种构建方式

    // 第一种
    val namesFlow = flowOf("Jody", "Steve", "Lance", "Joe")
    
    // 第二种
    val namesFlow = listOf("Jody", "Steve", "Lance", "Joe").asFlow()
    
    // 第三种
    val namesFlow = flow {
      val names = listOf("Jody", "Steve", "Lance", "Joe")
      for (name in names) {
        delay(100)
        emit(name)
      }
    }
    

    emit 是向消费端发送数据;flow 后面的大括号代表一个 lambda block。

    flow operator

    fun main() = runBlocking {
      namesFlow
          .map { name -> name.length }
          .filter { length -> length < 5 }
          .collect { println(it) }
    
      println()
    }
    

    上面代码中,有两种 flow operator:

    • Intermediate Operator: map 和 filter。map 类似于 list 的 map 操作,将当前 list 转换一下;filter 过滤元素。注意:区分开 Intermediate(居中的,中间的) 和 Immediate(立即,马上)。所以,Intermediate Operator 作用于 flow 时,相关操作并不会立即执行。即其返回的依旧是 cold stream。
    • Terminal Operator: collect。flow 作为 cold stream, 其并不会产生数据,直到调用了 terminal operator。collect 是一个 suspending function, 所以需要在 coroutine 或者另一个 suspending function 中调用。

    Room 对 flow 的支持

    Room 2.2 版本开始支持 Flow。

    即 select 操作可以被监听,当 insert 或者 remove 数据到数据库时,可以被监听到。

    例如:

    @Query("SELECT * FROM forecasts_table")
    fun getForecasts(): Flow<List<DbForecast>>
    

    这里用到了前面提到的一种将 list 转换为 flow 的做法。

    Flow 与 LiveData

    Android 中有三种常见的 observer pattern:LiveData, Flow, RxJava:

    • LiveData: 用法最简单,上手容易。非常适合 View 与 ViewModel 间同步状态。但是处理不了更复杂的场景。LiveData 是不防抖的。LiveData 的 transformation 工作在主线程
    • RxJava: 功能最强大,但上手最难。
    • Flow:鉴于两者之间。flow 相对 livedata 的优势是,设置数据,不需要切换到主线程。而 LiveData 的 setValue() 发生在主线程(非主线程调用会抛异常,postValue() 内部会切换到主线程调用 setValue())。且 Flow 是 koltin 语言层支持,支持跨平台;而 LiveData 不支持跨平台。

    LiveData 相对于 Flow 的优势:内置了生命周期的管理,所以非常适合 View 与 ViewModel 间的通信。

    为何如此看重生命周期管理?
    假设我们的程序已经不在前台了,flow 流还在持续更新,则UI更新依然在持续进行当中。这是非常危险的事情,因为在非前台的情况下更新UI,某些场景下是会导致程序崩溃的。

    但是我感觉 90% 的场景都适用于 LiveData,即基本都是用在界面状态同步上。偶尔才会有类似手机传感器数据这种复杂的处理场景。

    Flow 转换为 LiveData

    .asLiveData()
    

    例如:

    //1
    val forecasts: LiveData<List<ForecastViewState>> = weatherRepository
        //2
        .getForecasts()
        //3
        .map {
          homeViewStateMapper.mapForecastsToViewState(it)
        }
        //4
        .asLiveData()
    

    flow 适合做搜索的地方

    private val _locations = queryChannel
        //1
        .asFlow()
        //2
        .debounce(SEARCH_DELAY_MILLIS)
        //3
        .mapLatest {
          if (it.length >= MIN_QUERY_LENGTH) {
            getLocations(it)
          } else {
            emptyList()
          }
        }
        //4
        .catch {
          // Log Error
        }
    
    
    • 将 channel 转换为 flow。实际这部分可以用 StateFlow 替代 Channel 尝试一下。
    • 防抖动:debounce 用于防抖动。例如,实时搜索功能,当用户输入一个单词的时候,不需要每个字母都触发网络搜索,希望能加上触发的时间间隔,以避免高频率访问网络接口。
    • mapLatest(): 处理最新的数据,并把之前没有完成的计算 cancel 掉。

    StateFlow 替代 Channel

    BroadcastChannel 未来会在 Kotlin 1.6.0 中弃用,在 Kotlin 1.7.0 中删除。它的替代者是 StateFlow 和 SharedFlow。

    StateFlow 和 SharedFlow 是 hot stream。

    例如,实时搜索词进行查询的场景: View 通过 channel 将查询词传递给 viewmodel 层,viewmodel 中将 channel 转换为 flow, 再 map 进行查询。这里的 channel 就可以用 flow 替代。

    参考

    • https://www.kodeco.com/9799571-kotlin-flow-for-android-getting-started
    • https://kotlinlang.org/docs/flow.html
    • https://www.baeldung.com/kotlin/flows-vs-channels

    关于作者 🌱

    我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式