Android开发[2]:Flow

Flow

今日核心目标

  • 掌握Flow基础核心用法、callbackFlow/channelFlow实战技巧,解决数据监听、回调嵌套痛点。
  • 掌握Flow进阶用法(节流、防抖、StateFlow/SharedFlow),解决高频数据发射、多页面共享数据的痛点。
  • 将Flow与协程无缝衔接,形成完整的异步处理+状态管理体系,进一步提升代码性能和工程化水平。

Flow知识点

  • Flow基础核心
  • Flow核心操作符
  • callbackFlow与channelFlow实战

Flow基础核心

  • 定义:Flow是Kotlin用于处理异步数据流的工具,基于协程实现,支持冷流特性(只有被收集时才发射数据)。适合处理连续的异步数据,替代Callback解决回调嵌套问题。
    • 例:蓝牙实时数据、传感器数据。
  • 优势:相比Callback,代码简洁、可读性强,支持链式调用,且与协程无缝联动。适合处理连续数据发射场景。
    • 例:蓝牙持续接收数据。
  • 用法:通过flow构建数据流,用emit发射数据。通过collect收集数据,收集过程会挂起协程,不阻塞主线程,适配Android UI操作和耗时操作场景。

Flow核心操作符

  • map:数据转换。适合将原始字节数据转换为实体类(例:网络请求数据、蓝牙数据解析)。
    • 不改变数据流的发射节奏
  • filter:数据过滤。适合过滤无效数据,只保留符合条件的数据。
    • 例:过滤蓝牙信号强度低于-80dBm的设备数据
  • catch:异常捕获。结合异常处理器,捕获Flow发射、处理过程中的异常,避免App闪退。
    • 例:网络请求异常、蓝牙中断导致数据发射失败
  • collectLatest:最新值收集。适合数据实时更新场景,提升性能。
    • 例:蓝牙信号强度实时变化,只收集最新的信号值,取消之前未处理的收集任务

callbackFlow与channelFlow实战

  • callbackFlow:专门用于桥接传统回调式API,将回调转换为Flow数据流,自动处理资源清理,避免回调嵌套。
    • 通过callbackFlow{}构建,用trySend()非阻塞发射数据
    • 通过awaitClose()在Flow取消时清理资源
      • 例:取消网络请求、注销蓝牙回调
  • channelFlow:用于从多个协程并发发射数据,支持跨协程发射。
    • 适合多设备蓝牙数据并发接收、单页面多模块数据并发请求场景
    • 可整合多个异步数据源,通过send()发射数据,awaitClose()等待所有发射完成并清理资源
callbackFlow与channelFlow区别
  • callbackFlow:侧重回调转Flow,解决回调嵌套。
  • channelFlow:侧重并发数据源整合,解决多协程并发发射数据问题。
蓝牙开发中的使用
  • callbackFlow包装蓝牙Gatt回调,将蓝牙连接状态、数据接收转换为Flow
  • channelFlow整合多个蓝牙设备的数据流,实现并发处理,提升效率。

Flow进阶用法

  • Flow节流与防抖(解决高频数据痛点)
  • StateFlow与SharedFlow(解决多页面数据共享)
  • Flow内存泄漏防控(规避线上问题)

Flow节流与防抖(解决高频数据痛点)

  • 场景
    • 蓝牙设备会高频发射数据(例:蓝牙音频高频传输、传感器数据、实时信号强度)。
    • NDK多任务并行解析会高频返回结果。
    • 若不做控制,会导致UI频繁刷新、数据处理卡顿、资源浪费,甚至引发ANR,节流与防抖即为解决方案。
  • 节流(throttleLatest/sample):控制数据处理频率,在指定时间内只处理一次数据(或只取最新数据)。
    • 适合蓝牙高频数据监听场景(例:每100ms只处理一次蓝牙信号强度,避免UI频繁刷新)。
      • throttleLatest:在指定时间窗口内取最新值。
      • sample:在指定时间窗口内只取一次值。
  • 防抖:等待数据稳定后再处理,若在指定时间内有新数据发射,则重新计时。
    • 适用于蓝牙设备连接状态频繁切换、NDK解析结果频繁返回的场景(例:蓝牙连接时"连接中-断开-连接中"的频繁切换,防抖后只处理最终稳定的连接状态)。
  • 坑点:节流和防抖误用
    • 蓝牙数据接收场景用防抖,导致关键数据丢失。
    • 连接状态切换用节流,导致状态更新不及时。
    • 未结合协程线程池,导致节流防抖操作阻塞主线程。
kotlin 复制代码
/**
 * 节流:指定时间内取最新值,适合高频数据监听
 */
fun <T> Flow<T>.throttleLatest(milliseconds: Long): Flow<T> = this
    .throttleLatest(milliseconds)
    .flowOn(multiTaskDispatcher) // 绑定协程池,避免阻塞主线程
    .catch { throwable ->
        coroutineHandler.handleException(multiTaskDispatcher, throwable)
    }
    
/**
 * 节流:指定时间内取一次值,适合高频数据
 */
fun <T> Flow<T>.sample(milliseconds: Long): Flow<T> = this
    .sample(milliseconds)
    .flowOn(multiTaskDispatcher) // 绑定协程池,避免阻塞主线程
    .catch { throwable ->
        coroutineHandler.handleException(multiTaskDispatcher, throwable)
    }

/**
 * 防抖:等待数据稳定后处理
 *
 * 适合蓝牙连接状态、NDK解析结果
 */
fun <T> Flow<T>.debounce(milliseconds: Long): Flow<T> = this
    .debounce(milliseconds)
    .flowOn(multiTaskDispatcher) // 绑定协程池,避免阻塞主线程
    .catch { throwable ->
        // 结合全局异常处理,避免闪退
        coroutineHandler.handleException(multiTaskDispatcher, throwable)
    }

StateFlow与SharedFlow(解决多页面数据共享)

  • 定位:二者均是Kotlin Flow的热流,无论是否被收集,都会发射数据,持有数据状态。
    • 区别于Flow冷流(只有被收集才发射数据)。
    • 专门用于多页面数据共享、状态管理。
  • StateFlow:状态容器,必须有初始值,始终持有最新的单个状态值。
    • 新观察者注册时会立即收到当前最新值,适合管理单一状态
      • 例:网络请求状态、蓝牙全局连接状态。
    • 状态管理的首选,贴合蓝牙全局状态同步场景。
    • 默认配置:replay=1仅缓存最新值、无额外缓冲区,数据更新采用覆盖式,新值会替换旧值。
  • SharedFlow:通用共享数据流,可选初始值。
    • 可配置缓存策略replay缓存数量、缓冲区大小。
    • 适合分发一系列事件或多源数据
      • 例:单页面多模块数据请求、蓝牙多设备数据、NDK多任务解析日志。
    • 新观察者默认只收到注册后的新数据(可通过replay配置回溯历史数据)。
    • 适合多页面共享连续数据流。
StateFlow与SharedFlow区别
  • StateFlow
    • 侧重单一状态管理,让所有页面都能实时获取最新状态
      • 例:网络请求状态、蓝牙是否连接、NDK解析是否完成。
    • 必须有初始值
    • 新观察者能拿到最新值
  • SharedFlow
    • 侧重多源事件分享
      • 例:蓝牙多设备数据、NDK解析日志流。
    • 初始值可选,新观察者默认不能收到最新值(需配置replay)
    • 分发蓝牙多设备实时数据、NDK多任务解析结果,实现多页面同步接收数据,解决多页面数据共享频繁、状态不一致问题。

Flow内存泄漏防控(规避线上问题)

  • 泄露场景
    • 用GlobalScope收集Flow
    • Flow收集未与页面生命周期绑定
    • StateFlow/SharedFlow未及时取消收集
    • 持有Activity/Fragment引用导致无法回收
  • 防控方案
    • 使用lifecycleScope、viewModelScope收集Flow
    • 确保页面销毁、ViewModel清除时,Flow收集自动取消
    • 避免在Flow中持有Activity/Fragment的强引用,可用弱引用或通过状态流间接更新UI
    • StateFlow/SharedFlow需合理管理生命周期,避免长期持有不必要的引用
适配

将Flow收集与页面生命周期、ViewModel生命周期绑定。封装形成数据处理+异常捕获+内存防控体系。

kotlin 复制代码
/**
 * Flow与协程联动:页面绑定生命周期,避免泄漏
 */
fun <T> Flow<T>.bindLifecycle(activity: Activity): Flow<T> = this
    .onStart {
        // 页面启动时收集
    }
    .onCompletion {
        // 页面销毁时取消收集
        if (activity.isDestroyed) {
            currentCoroutineContext().cancel(CancellationException("$activity isDestroyed"))
        }
    }
/**
 * 观察者绑定生命周期
 */
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        flowRepository.getTestsFlow()
            .catch { e ->
                LogTool.e("MainActivity", "callbackFlow error: ${e.message}")
            }
            .collect { tests ->
                LogTool.i("MainActivity", "callbackFlow tests: $tests")
            }
    }
}
相关推荐
zzb15802 小时前
Android Activity 与 Intent 学习笔记
android·笔记·学习
studyForMokey2 小时前
【Android面试】动画 & Bitmap
android·面试·职场和发展
黑牛儿3 小时前
面试高频问题:从浏览器请求到PHP响应:完整流程拆解
android·后端·面试·php
y小花4 小时前
安卓USB服务概述
android·usb
alexhilton12 小时前
Compose中初始加载逻辑究竟应该放在哪里?
android·kotlin·android jetpack
zh_xuan12 小时前
启动RN服务端口被占用
android·react native
Code-keys15 小时前
Android Codec2 Filter 算法模块开发指南
android·算法·音视频·视频编解码
y = xⁿ16 小时前
MySQL:count(1)与count(*)有什么区别,深分页问题
android·数据库·mysql
程序员陆业聪18 小时前
Android启动全景图:一次冷启动背后到底发生了什么
android