Kotlin之数据流Flow-Kotlin在Android中的使用(六)

简介

Google官方描述:
Flow是基于协程构建的数据流,数据流是可通过异步方式进行计算处理的一组数据序列。所发出值的类型必须相同。例如Flow 是发出整数值的数据流。
数据流与生成一组序列值的 Iterator 非常相似,但它使用挂起函数通过异步方式生成和使用值。这就是说,例如,数据流可安全地发出网络请求以生成下一个值,而不会阻塞主线程。

数据流包含三个实体:

  • 提供方会生成添加到数据流中的数据。得益于协程,数据流还可以异步生成数据。
  • (可选)中介可以修改发送到数据流的值,或修正数据流本身
  • 使用方则使用数据流中的值。
    在这里插入图片描述
    在Adnroid中,通常从网络或本地数据库获取数据,经过业务逻辑处理后,最终在View上展示,使用Flow可将获取数据的来源作为提供方,View作为使用方,其他的业务逻辑处理作为中介来修改数据流

基本使用

创建数据流

创建数据流的函数:

  • flowOf(…)函数根据一组固定的值创建流。
  • asFlow()扩展函数可以将各种类型的函数转换为流(例如LiveData)。
  • flow {…} 构建器函数,用于构造从顺序调用到发出函数的任意流。
  • channelFlow {…} 构建器函数构造从潜在并发调用到send函数的任意流,热流,可实现实现异步非阻塞的生产者消费者模型

流创建示例:

// 下面示例代码统一使用该数据提供方
val producer: Flow<Int> = flow {
    for (i in 1..10) {
        delay(1000)// 等待一定间隔
        emit(i)
    }
}
val producer2: Flow<IntRange> = flowOf(1..10)
val producer3: Flow<Int> = (1..10).asFlow()
val producer4: Flow<Int> = channelFlow<Int> {
    for (i in 1..10) {
        delay(1000)
        send(i) //emit 变为 send
    }
}

数据流是有序的。当协程内的提供方调用挂起函数时,提供方会挂起,直到挂起函数返回

修改数据流

中介可以利用中间运算符在不使用值的情况下修改数据流。这些运算符都是函数,可在应用于数据流时,设置一系列暂不执行的链式运算,留待将来使用值时执行。
这里列出部分常用到的运算符:

  • map:将结果进行修改
  • filter:对结果集添加过滤条件
  • flowOn:线程切换
  • take:限制 emit() 发射的次数
  • zip:合并两个flow
  • combine:合并两个flow,并最终返回合并的Flow,其值是通过组合每个流最近发射的值并使用变换函数生成的
  • conflate:可忽略发送的数据
  • debounce:过滤掉给定timeout内的值,始终会发出最新值
  • tranform:没有附加功能,根据代码逻辑发射数据

map示例

val intermediary: Flow<Int> = producer 
    .map { data -> data * 100 }

// 通过map运算符将发射的数据 *100 流数据: 100 200 300...

filter示例

val intermediary: Flow<Int> = producer
    .filter { it < 5 }
// 通过filter运算符过滤,只发射小于5的值,流数据:1 2 3 4 5

flowOn

val intermediary: Flow<Int> = producer
    .filter { it < 5 }
    .map { data -> data * 100 }
    .flowOn(Dispatchers.IO)
// 指定代码块中的代码运行在IO线程,运算符是可以结合使用的

take示例

val intermediary: Flow<Int> = producer
    .filter { it < 5 }
    .map { data -> data * 100 }
    .take(3)
// 限制发射次数为3 流数据: 100 200 300

zip示例

val producer: Flow<Int> = flow {
    for (i in 1..10) {
        delay(1000)// 等待一定间隔
        emit(i)
    }
}
val producer2: Flow<String> = flow {
    for (i in 10..20) {
        delay(500)// 等待一定间隔
        emit("producer2:$i")
    }
}
val intermediary: Flow<String> = producer.zip(producer2) {
    i, s -> i.toString() + s
}
// 合并producer和producer2,发射两个值的拼接字符串,流数据:1producer2:10 2producer2:11 3producer2:12...

combine示例

val producer: Flow<Int> = flow {
    for (i in 1..10) {
        delay(10)// 等待一定间隔
        emit(i)
    }
}
val producer2: Flow<String> = flow {
    for (i in 10..20) {
        delay(1000)// 等待一定间隔
        emit("producer2:$i")
    }
}

val intermediary: Flow<String> = producer.combine(producer2) {
    i, s -> i.toString() + s
}

// 合并producer和producer2,取两个flow最近发射的值拼接字符串,流数据:10producer2:10,10producer2:11...

conflate示例

val producer: Flow<Int> = flow {
    for (i in 1..10) {
        delay(1000)// 等待一定间隔
        emit(i)
    }
}
val intermediary: Flow<Int> = producer.conflate().onEach { delay(3000) }
// 生产速度大于消费速度,忽略部分数据,流数据:1 3 6 9

debounce示例

val producer: Flow<Int> = flow {
    for (i in 1..10) {
        delay(1000)// 等待一定间隔
        emit(i)
    }
}.debounce(1500)
// 过滤掉给定timeout内的值,始终会发出最新值,流数据:1 3 4 6...

tranform示例

val producer: Flow<Int> = flow {
    for (i in 1..10) {
        delay(1000)// 等待一定间隔
        emit(i)
    }
}
val intermediary: Flow<String> = producer.transform {
    emit("transform$it")
}
// 代码块中写什么就发射什么,流数据:transform1 transform2...
使用数据流

Flow流数据是冷的,所以 collect 方法被调用后 flow 内的方法体才会被调用

  • collect :消费数据,其他使用数据流方法最终也会调用它,是一个suspend方法,会挂起当前协程
  • collectLatest:collectLatest块中的执行时间大于emit发射的间隔时间,那么将会被emit 打断
  • toList:把数据消费到一个 List 列表中
  • toSet:把数据消费到一个 Set列表中
  • frist:获取第一个元素
  • reduce:对当前要消费的值和之前计算的值进行计算,得到新值返回。所有值消费完成后返回最终值

collect示例

// 下面示例代码统一使用该数据提供方
val producer: Flow<Int> = flow {
    for (i in 1..10) {
        delay(1000)// 等待一定间隔
        emit(i)
    }
}

suspend fun FlowTest() {
    producer.collect { data ->
        print("result: $data")
    }
}
// 接收发射的数据 结果:result:1 result:2...

collectLatest示例

flow {
     emit(1)
     delay(50)
     emit(2)
 }.collectLatest { value ->
     println("延迟前执行:$value")
     delay(100) // 延时
     println("延迟后执行:$value")
 }      	 
// 延迟大于发射间隔的50ms被打断 结果 "延迟前执行:1" ,"延迟前执行:2" "延迟后执行:2"

toList示例

suspend fun FlowTest():List<Int> {
    return producer.filter { it % 2 == 0 }.toList()
}
// 过滤发射的偶数放入List<Int>数组中

frist示例

suspend fun FlowTest(): Int {
    return producer.filter { it % 2 == 1 }.first()
}
// 接收第一个发射的奇数,结果:1

reduce示例

suspend fun FlowTest(): Int {
    return producer.reduce { accumulator, value ->
        accumulator + value
    }
}
// 将发射的值累加,并返回,结果:1 + 2 + 3... + 10
数据流捕获异常

流实现永远不会捕获或处理下游流中发生的异常。从实现的角度来看,这意味着永远不要将对emit和EmittAll的调用包装为 try { … } catch { … }块。流中的异常处理应由catch运算符执行, 并且旨在仅捕获来自上游流的异常,同时传递所有下游异常。同样,终端操作员如collect会 抛出在其代码或上游流中发生的任何未处理的异常
Flow 可以使用传统的 try…catch 来捕获异常:

val producer: Flow<Int> = flow {
    for (i in 1..10) {
        delay(1000)// 等待一定间隔
        emit(i)
    }
}
suspend fun FlowTest() {
    producer.map { it * 10 }
        .catch {  }// 捕获flow代码块和第一个map中的异常
        .map { it + 1 }
        .collect{ print(it) }
}

发生异常时,系统不会调用 collect lambda 参数,因为未收到新数据项。catch 还可执行 emit 操作,向数据流发出数据项

suspend fun FlowTest() {
    producer.map { it * 10 }
        .catch { emit(100) }// 这里的100,实际代码中,可在网络数据出问题时去获取缓存
        .map { it + 1 }
        .collect{ print(it) }
}	
Jetpack 库中的数据流

Flow和LiveData可以通过扩展函数asLiveData(),asFlow()相互转换
许多 Jetpack 库已集成数据,Room数据库可以在数据更新时通过Flow通知更改
以Google sunflower 项目中的代码片段示例:

将数据库查询结果定义为Flow

@Dao
interface GardenPlantingDao {
    // ...省略代码

    /**
     * 这个查询将告诉Room查询[Plant]和[GardenPlanting]表并处理对象映射。
     */
    @Transaction
    @Query("SELECT * FROM plants WHERE id IN (SELECT DISTINCT(plant_id) FROM garden_plantings)")
    fun getPlantedGardens(): Flow<List<PlantAndGardenPlantings>>

    // ...省略代码
}

仓库获取数据

@Singleton
class GardenPlantingRepository @Inject constructor(
    private val gardenPlantingDao: GardenPlantingDao
) {

   // ...省略代码
    fun getPlantedGardens() = gardenPlantingDao.getPlantedGardens()
}

viewModel获取数据并转换为LiveData

@HiltViewModel
class GardenPlantingListViewModel @Inject internal constructor(
    gardenPlantingRepository: GardenPlantingRepository
) : ViewModel() {
    val plantAndGardenPlantings: LiveData<List<PlantAndGardenPlantings>> =
        gardenPlantingRepository.getPlante/dGardens().asLiveData()
}

数据变化时在Fragment中更新UI

@AndroidEntryPoint
class GardenFragment : Fragment() {

    private lateinit var binding: FragmentGardenBinding

    private val viewModel: GardenPlantingListViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // ...省略代码
    }

    private fun subscribeUi(adapter: GardenPlantingAdapter, binding: FragmentGardenBinding) {
        viewModel.plantAndGardenPlantings.observe(viewLifecycleOwner) { result ->
            binding.hasPlantings = !result.isNullOrEmpty()
            adapter.submitList(result)
        }
    }

    // ...省略代码
}

这里在表现层中ViewModel 向 view 暴露 LiveData,LiveData 的主要职责是更新 UI,使用StateFlow也能达到相同的目的

StateFlow
StateFlow是一个状态容器式可观察数据流,可以向其收集器发出当前状态更新和新状态更新。还可通过其 value 属性读取当前状态值。如需更新状态并将其发送到数据流
官方示例代码片段:

MutableStateFlow

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    // 支持属性以避免来自其他类的状态更新
    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    // UI从这个StateFlow收集数据以获得它的状态更新
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // 更新视图与最新的喜爱的新闻
                // 写入MutableStateFlow的value属性,
                // 向流中添加新元素并更新所有元素
                // 它的收集
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

// 表示LatestNews屏幕的不同状态
sealed class LatestNewsUiState {
    data class Success(news: List<ArticleHeadline>): LatestNewsUiState()
    data class Error(exception: Throwable): LatestNewsUiState()
}

与处理任何其他数据流一样,View 会监听 StateFlow

class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // 在生命周期范围内启动一个协程
        lifecycleScope.launch {
            // 每当生命周期处于STARTED(或以上)状态时,
            // repeatOnLifecycle就会在一个新的协程中启动该块,并在它停止时取消它。
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // 触发流并开始监听值。
                // 注意,这发生在生命周期为STARTED时,
                // 并在生命周期为STOPPED时停止收集
                latestNewsViewModel.uiState.collect { uiState ->
                    // New value received
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

SharedFlow
SharedFlow可以一对多,发射一次,多端接收

// Class 它在应用程序的内容需要刷新时进行集中
class TickHandler(
    private val externalScope: CoroutineScope,
    private val tickIntervalMs: Long = 5000
) {
    // 支持属性以避免来自其他类的流发射
    private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
    val tickFlow: SharedFlow<Event<String>> = _tickFlow

    init {
        externalScope.launch {
            while(true) {
                _tickFlow.emit(Unit)
                delay(tickIntervalMs)
            }
        }
    }
}

class NewsRepository(
    ...,
    private val tickHandler: TickHandler,
    private val externalScope: CoroutineScope
) {
    init {
        externalScope.launch {
            // 监听更新标记
            tickHandler.tickFlow.collect {
                refreshLatestNews()
            }
        }
    }

    suspend fun refreshLatestNews() { ... }
    ...
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值