前言打开AndroidArchitectureComponents页面,我们可以看到一些新发布的jetpack组件,比如Room、DataStore、Paging3、DataBinding等,都支持Flow。谷歌开发者账号最近也发表了几篇使用Flow的文章,比如:MigratingfromLiveDatatoKotlinDataFlow官方似乎是强烈推荐使用Flow而不是LiveData,那么问题来了,有必要吗?LiveData用的不错,有必要再学Flow吗?本文主要回答这个问题,包括以下内容:LiveData有哪些缺点?Flow简介以及为什么会有FlowSharedFlow和StateFlow以及它们之间的区别本文具体内容如下:1.LiveData有什么缺点?1.1为什么引入LiveData?要了解LiveData的缺点,我们先来了解LiveData为什么要引入LiveData。LiveData的历史可以追溯到2017年,当时观察者模式有效简化了开发,但是RxJava等库对新手来说有点太复杂了。为此,ArchitectureComponents团队创建了LiveData:一种适用于Android的具有自主生命周期感知的可观察数据存储类。LiveData在设计上有意简化,方便开发者上手;而对于更复杂的交互数据流场景,推荐大家使用RxJava,这样两者结合的优势才会发挥出来。可以看出LiveData是一个简单易用,具有生命周期感知能力的观察者模式,使用起来非常简单,这是它的优势,也是它的劣势,因为处理的越复杂越麻烦交互式数据流场景。1.2LiveData的不足上面我们提到了LiveData结构简单,但是功能不够强大。它有以下缺点:LiveData只能在主线程更新数据。线程更新数据,可能有同学会问,不是有postValue吗?其实postValue也需要切换到主线程,如下图:这意味着当我们要更新LiveData对象时,我们会经常换线程(Worker线程→主线程),如果你想修改完LiveData再切换回worker线程,会比较麻烦,postValue可能会丢失数据。2.Flow介绍Flow是Kotlin协程和反应式编程模型相结合的产物。你会发现它和RxJava非常相似,两者之间有相互转换的API,使用起来非常方便。2.1为什么引入Flow为什么引入Flow,我们可以从Flow解决什么问题的角度切入。LiveData不支持线程切换。所有数据转换都将在主线程上完成。有时需要频繁更换线程,处理复杂的数据流。有点难。而且RxJava有点太繁琐了。运营商很多,傻傻的,容易混淆,入门门槛高。同时,你需要自己处理生命周期。可以看到Flow是介于LiveData和RxJava之间的一种解决方案,它有以下特点Flow支持线程切换,背压Flow入门门槛很低,没有那么多傻傻的算子不清楚简单的数据转换和算子,比如map等。数据流不产生数据,不消费,这点和LiveData不同:LiveData的发送方不依赖于接收方。它是kotlin协程的一部分,可以与协程基础设施很好地结合。Flow的使用比较简单。有兴趣的同学可以参考文档:Flow文档3.SharedFlow介绍上面我们介绍过,Flow是冷流,什么是冷流?冷流:只有当订阅者订阅时,发出数据流的代码才开始执行。而且冷流和订阅者之间只能是一对一的关系。当有多个不同的订阅者时,消息被完全重发。也就是说,对于冷流来说,当有多个订阅者时,他们各自的事件是独立的。HotStream:无论有没有订阅者,事件总是发生。当热点流有多个订阅者时,热点流与订阅者之间是一对多的关系,可以与多个订阅者共享信息。3.1为什么要引入SharedFlow,上面已经说的很清楚了。冷流和订阅者只能是一对一的关系。当我们要实现一流多订阅者的需求时(这在开发中很常见),就需要热流。从命名上也很容易理解。SharedFlow是共享Flow,可以实现一对多的关系。SharedFlow是一种热流。replay:Int=0,extraBufferCapacity:Int=0,onBufferOverflow:BufferOverflow=BufferOverflow.SUSPEND):MutableSharedFlow主要有3个参数replay表示当一个新的订阅者采集时,发送几个已经发送给它的数据,默认为0,表示新订阅者默认不会获取到之前的数据。extraBufferCapacity表示MutableSharedFlow减去replay后还有多少数据缓存,默认为0。简单用法如下://ViewModelvalsharedFlow=MutableSharedFlow()viewModelScope.launch{sharedFlow.emit("Hello")sharedFlow.emit("SharedFlow")}//ActivitylifecycleScope.launch{viewMode.sharedFlow.collect{print(it)}}3.3将冷流转换为SharedFlow普通流,可以使用shareIn扩展方法将其转换为SharedFlowvalsharedFlowbylazy{flow{//...}.shareIn(viewModelScope,WhileSubscribed(500),0)}shareIn主要有三个参数:@paramscope共享开始的协程作用域@paramstarted控制分享开始和结束的策略@paramreplay状态流的重播次数started接受以下三个值:Lazily:当第一个订阅者出现时开始,当scope指定的范围结束时终止Eagerly:立即开始,并在scope指定的范围结束时终止。whileSubscribed:这种情况有点复杂,后面会详细说明。对于那些只执行一次的操作,您可以使用Lazily或Eagerly。但是,如果需要观察其他流,则应使用WhileSubscribed来实现细微但重要的优化工作3.4Whilesubscribed策略WhileSubscribed策略将取消没有收集器的上游数据流。数据暴露给视图,视图还观察来自其他层或上游应用程序的数据流。保持这些流处于活动状态可能会导致不必要的资源浪费,例如不断从数据库连接、硬件传感器等读取数据。当您的应用程序在后台运行时,您应该保持克制并中止这些协程。publicfunWhileSubscribed(stopTimeoutMillis:Long=0,replayExpirationMillis:Long=Long.MAX_VALUE)如上所示,它支持两个参数:stopTimeoutMillis控制一个延时值,单位毫秒,指的是最后一个订阅者结束订阅和停止上行流的时间差。默认值为0(立即停止)。这个值很有用,因为你可能不想仅仅因为视图有几秒钟没有监听就结束上游流。这很常见——例如,当用户旋转设备时,原始视图首先被销毁,然后在几秒钟内重新创建。replayExpirationMillis表示数据回放的过期时间。如果用户离开应用程序的时间过长,此时您不希望用户看到过时的数据。您可以使用此参数。4.StateFlow介绍4.1为什么要介绍StateFlow?状态流?StateFlow是SharedFlow的一个特殊变体。StateFlow最接近LiveData,因为:它总是有价值的。它的价值是独一无二的。它允许多个观察者共享(因此共享流)。它总是只向订阅者重现最新的价值,而不管活跃观察者的数量。可以看出StateFlow和LiveData比较接近,可以获取到当前值。可以想象,之所以引入StateFlow,就是为了取代LiveData。总结如下:StateFlow继承自SharedFlow,是SharedFlow的一个特殊变体。StateFlow类似于LiveData。信不信由你推出来代替LiveData4.2StateFlow的简单使用。我们先看一下构造函数:publicfunMutableStateFlow(value:T):MutableStateFlow=StateFlowImpl(value?:NULL)StateFlow的构造函数比较简单,只需要传入一个默认值StateFlow,本质上就是一个SharedFlowwithreplay为1,没有buffer,所以第一次订阅时会优先获取默认值3。StateFlow只有在值已经更新且值发生变化时才会返回,也就是说,如果更新的最终值没有变化,则不会回调Collect方法。这与LiveData不同,与SharedFlow类似。我们也可以使用stateIn将普通流转化为StateFlowvalresult:StateFlow>=someFlow.stateIn(scope=viewModelScope,started=WhileSubscribed(5000),initialValue=Result.Loading)和shareIn类似,唯一不同的是需要传入一个默认值,之所以传入WhileSubscribed5000,是为了实现在等待5秒后仍然没有订阅者的情况下终止协程的功能,该方法有以下功能。用户将你的应用程序转至后台运行,所有其他层的数据更新将在5秒后停止,这样可以节省电量。最新数据仍将被缓存,因此当用户切换回应用程序时,视图将立即呈现数据。订阅将重新启动,将填充新数据,并在数据可用时更新视图。屏幕旋转时,因为重新订阅的时间在5s以内,所以不会暂停上行流量。4.3在页面上观察StateFlow和LiveData类似。我们还需要经常观察页面上的StateFlow。观察StateFlow需要在协程中,所以我们需要Coroutinebuilder,一般我们会使用如下lifecycleScope.launch:立即启动协程,当Activity或Fragment销毁时结束协程。LaunchWhenStarted和LaunchWhenResumed,会一直等到lifecycleOwner进入X状态,离开X状态时暂停协程,如上图所示:使用launch是不安全的,当lifecycleOwner进入时也会接收数据更新应用程序在后台,可能最好使用launchWhenStarted或launchWhenResumed导致应用程序崩溃。它在后台时不会接收数据更新。但是,当应用程序在后台运行时,上游数据流将保持活动状态,因此可能会浪费一定的资源。这样我们使用WhileSubscribed进行配置是不是就失效了呢?订阅者一直存在,只有在页面关闭过程中才会取消订阅,如下图所示。比如在一个Fragment的代码中:onCreateView(...){viewLifecycleOwner.lifecycleScope.launch{viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED){myViewModel.myUiState.collect{...}}}}STARTED状态当Fragment进入STOPPED状态时会采集流,当Fragment进入STOPPED状态时采集过程结束。结合使用repeatOnLifecycleAPI和WhileSubscribed可以帮助您的应用程序充分利用设备资源,同时发挥最佳性能。4.4在页面中观察Flow的最佳方式通过ViewModel暴露数据并在页面中获取数据的最佳方式是:使用whileSubscribed策略暴露带有超时参数的Flow。示例1使用repeatOnLifecycle来收集数据更新。示例2的最佳实践如上图所示。如果采用其他方式,上游数据流会一直保持活跃,造成资源浪费。当然,如果你不需要使用KotlinFlow的强大功能,使用LiveData即可:)5StateFlow和SharedFlow有什么区别?从上面可以看出,StateFlow和SharedFlow其实很相似,让人一头雾水。有时很难选择使用哪一个。让我们总结一下它们的区别。如下:SharedFlow配置比较灵活,支持配置replay,buffersize等。StateFlow是SharedFlow的一个特殊版本,replay固定为1,默认buffersize为0。StateFlow类似于LiveData,支持获取通过myFlow.value的当前状态。如果你有这个需求,你必须使用StateFlowSharedFlow支持发送和收集重复值,StateFlow不会在值重复时回调collect。对于新的订阅者,StateFlow只会重放当前的最新值。SharedFlow可以配置replay元素的个数(default),可以看出StateFlow已经为我们做了一些默认的配置,并且在SharedFlow上添加了一些默认的约束条件,这些配置不一定能满足我们的要求,它忽略了重复的值,是不可配置的。.这会造成一些问题,比如在List中添加元素并更新时,StateFlow会认为是重复值而忽略它需要初始值,在开始订阅时会回调初始值,这样可能不会是我们想要的,默认是粘性的,新用户订阅会获取最新的当前值,不可配置,SharedFlow可以修改replayStateFlow对SharedFlow的约束,这可能不是最适合你的,如果你不需要访问myFlow.value,享受SharedFlow的灵活性,你可以选择考虑使用SharedFlow总结简单往往意味着不够强大,但是强大通常意味着复杂,两者往往不相容。这种取舍在软件开发过程中经常面临。LiveData的简单不是它的缺点,而是它的特点。StateFlow和SharedFlow更强大,但学习成本也明显更高。我们应该根据自己的需要合理选择组件的使用。如果你的数据流比较简单,不需要线程切换和复杂的数据转换,LiveData就足够了。如果你的数据流比较复杂,你需要切换线程。等待操作,不需要发送重复值,需要获取myFlow.value,StateFlow是你不错的选择。如果你的数据流比较复杂,不需要获取myFlow.value,需要配置新用户订阅重播非质数,或者需要发送重复值,可以考虑使用SharedFlow