重磅更新!Kotlin协程1.11.0 发布,Flow/StateFlow 新 API 全面升级

本文首发于公众号"Android技术圈"

前言

kotlinx.coroutines 1.11.0 发布了。

这一版最直接的变化是 Kotlin 升到 2.2.20 ,同时新增了一些 Flow / StateFlow API,并修复 SharedFlow、flowOn、RxJava 互操作、R8 GC、JS/Wasm 异常处理相关问题。

Flow 和 StateFlow 推出新 API

这次有一组新的 Flow / StateFlow API,比较值得 Android 开发者注意的是这几个:

  • SharedFlow.asFlow():把 SharedFlow 暴露成普通 Flow 视图,隐藏"这是热流"的实现细节,目前还是 ExperimentalCoroutinesApi

  • StateFlow.onSubscription {}:给 StateFlow 增加订阅回调,并且返回值仍然是 StateFlow

  • StateFlow.collectLatest {} 的新重载:返回 Nothing,帮助编译器识别后面的代码不可达

  • Flow.associate / associateBy / associateWith / ...To:直接把 Flow 收集成 Map

如果你不想让外部代码依赖 StateFlow 的"当前值"和热流语义,1.11.0 以后可以显式把它收窄成普通 Flow

bash 复制代码
class DeviceRepository {
    private val _devices = MutableStateFlow<List<Device>>(emptyList())

    val devices: Flow<List<Device>> = _devices.asFlow()
}

这不是把热流变成冷流。它的作用是收窄类型:调用方只能按 Flow 收集,不能读取 .value,也不应该关心内部到底是 StateFlowSharedFlow,还是以后换成别的实现。

StateFlow.onSubscription 则适合放订阅侧的轻量动作,例如页面第一次开始观察时触发刷新:

bash 复制代码
val uiState: StateFlow<DeviceUiState> =
    repository.deviceState
        .onSubscription {
            refreshDevice()
        }

这里的重点是返回值仍然是 StateFlow<DeviceUiState>。以前你在 StateFlow 上接普通 Flow 操作符,很容易把类型变成 Flow,后续如果还要保留状态语义,就得额外封装。

新的 Map 终止操作符则更像补齐集合 API 的体验。以前要把一次性 Flow 结果整理成索引 Map,常见写法是先 toList()associateBy()

bash 复制代码
val devicesById: Map<String, Device> =
    repository.loadDevices()
        .associateBy { device -> device.id }

这类 API 和 stateInshareIn 不是一个层面的东西:它不会改变共享、订阅、replay 这些语义,只是让"收集完成后变成 Map"的终止操作更直接。

SharedFlow 修复更贴近事件流

SharedFlow 经常被用来做一次性事件。

比如 Toast、导航、弹窗、Snackbar、蓝牙扫描结果、设备连接状态变化:

bash 复制代码
private val _events = MutableSharedFlow<DeviceEvent>(
    replay = 0,
    extraBufferCapacity = 1
)

val events: SharedFlow<DeviceEvent> = _events

fun onConnectFailed(reason: String) {
    _events.tryEmit(DeviceEvent.ShowError(reason))
}

SharedFlow 的坑一般出现在边界上:有没有订阅者、buffer 满了怎么办、replay 是否会把旧事件重新发给新页面、取消时是否还有悬挂 collector。

这次 release 明确提到修复 SharedFlow 相关问题。对应用开发者来说,最应该回归的是三类场景。

第一类是页面重建。

Android 上旋转屏幕、进后台再回来、Compose navigation 切换页面,都可能让 collector 重新订阅。如果事件流设置不合理,用户会看到旧 Toast 重放、旧导航再次触发。

第二类是订阅时序。

事件先发出,collector 后启动,replay = 0 时事件本来就可能丢;如果业务不能接受丢事件,就不应该用裸 SharedFlow 当可靠消息队列。

第三类是高频事件。

扫描、传感器、日志、下载进度这类流量大,extraBufferCapacity 和 overflow 策略会直接影响内存和丢弃行为。

升级后建议把 SharedFlow 用法扫一遍:

bash 复制代码
MutableSharedFlow<Event>(
    replay = 0,
    extraBufferCapacity = 64,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

这段配置不是模板。它只说明三个参数必须一起看:是否重放、额外缓存多大、满了以后丢旧值还是挂起发送者。

flowOn 修复影响线程边界

flowOn 是 Flow 里最容易被误解的 API 之一。

它改变的是上游执行上下文,不是整条链路的所有代码。

bash 复制代码
val flow = flow {
    emit(api.fetchUser())
}
    .map { user -> user.toUiModel() }
    .flowOn(Dispatchers.IO)
    .onEach { ui ->
        render(ui)
    }

这里 flow {}mapDispatchers.IOonEach 在 collector 所在上下文。

flowOn 相关 bug 修复对真实项目有影响,因为很多项目会把网络、数据库、文件读取包进 Flow,再通过 flowOn(Dispatchers.IO) 切线程。

升级后重点回归这些位置:

  • flowOn 前后有没有依赖 ThreadLocal / MDC / tracing context

  • • 上游异常是否还能被下游 catch 捕获

  • • collector 取消后,上游 IO 是否及时停止

  • • 多个 flowOn 叠加时,线程是否符合预期

不要在 Flow 里同时到处写 withContext(Dispatchers.IO)flowOn(Dispatchers.IO)。它们表达的边界不同,混用会让代码难测。

更清晰的写法是把数据源放在上游,然后让 flowOn 明确标出上游线程:

bash 复制代码
fun observeUser(): Flow<User> {
    return flow {
        emit(userApi.fetchUser())
        emitAll(userDao.observeUser())
    }.flowOn(Dispatchers.IO)
}

UI 层只关心收集,不要再猜数据源在哪个线程执行。

最后

kotlinx.coroutines 1.11.0 不是只给 Kotlin 2.2.20 换个适配版本。

它更像一次底层稳定性更新:Flow / StateFlow 继续补 API,SharedFlow、flowOn、RxJava、R8、JS/Wasm 这些真实工程里的边界问题被继续修掉。

#Kotlin #Android开发 #协程 #Flow #KotlinMultiplatform

相关推荐
网安Ruler2 小时前
安卓逆向入门到入狱学习2
android·学习
Jomurphys2 小时前
Compose 组件 - 流式布局 FlowLayout(FlowColumn、FlowRow)
android·compose
帅次2 小时前
Navigation Compose:NavHost、NavController 与参数
android·kotlin·gradle·android jetpack·compose
程序员陆业聪2 小时前
架构哲学与工程化:从开发体验到CI/CD的全维度对比|跨平台框架深度对决(三)
android
程序员陆业聪2 小时前
Android网络全链路拆解:一次HTTP请求背后的性能陷阱
android
程序员陆业聪2 小时前
渲染引擎与性能拆解:自绘vs原生渲染vs Bridge的终极对决|跨平台框架深度对决②
android
程序员陆业聪10 小时前
技术选型决策树:什么团队、什么项目该选什么框架 | 跨平台框架深度对决(4)
android
星辰徐哥11 小时前
Rust异步测试与调试的实践指南
android·java·rust
星河耀银海11 小时前
C++ 运算符重载:自定义类型的运算扩展
android·java·c++