前言
这篇文章介绍Koltin中的Flow如何安全的收集数据流以及原理,这里的安全本质上指的是Flow需要管理自己的生命周期以适配Activity或者Fragment生命周期。其中一个case是,Activity退出了,Flow也应该退出收集数据流
危险行为和解法
下面是摘抄自使用 Kotlin Flow 构建数据流 "管道" 这篇文章中的代码。
kotlin
class MessagesViewModel : ViewModel() {
val userMessage: StateFlow<UiState> = getUserMessage().stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UiState.empty())
}
class MessagesActivity : AppCompatActivity() {
val viewModel: MessagesViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
// ❌ 危险的操作
lifecycleScope.launch {
viewModel.userMessage.collect { messages ->
listAdapter.submitList(messages)
}
}
// ❌ 危险的操作
LifecycleCoroutineScope.launchWhenX {
flow.collect { ... }
}
}
}
- 第一个危险操作是flow的上游和下游(collect)均和Activity的生命周期一致了,上游和下游的关闭操作均在
onDestory
之后了 - 第二个危险操作,只是在started时开始收集,上游在stop后还是会保持活跃状态
正常解法应该是
markdown
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED)
viewModel.userMessages.collect { messages ->
listAdapter.submitList(messages)
}
}
}
- 上述代码实现的效果是,activity退出后台停止上游和下游,并在activity显示的(STARTED)开启上游和下游
原理
先看上游, 上游的生命周期是由stateIn管理
stateIn
kotlin
public fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T> {
// stateFlow是用shareFlow实现的, replay = 1的shareFlow
val config = configureSharing(1)
val state = MutableStateFlow(initialValue)
// config.upstream == this
scope.launchSharing(config.context, config.upstream, state, started, initialValue)
return state.asStateFlow()
}
kotlin
// Launches sharing coroutine
private fun <T> CoroutineScope.launchSharing(
context: CoroutineContext,
upstream: Flow<T>,
shared: MutableSharedFlow<T>,
started: SharingStarted,
initialValue: T
) {
launch(context) { // the single coroutine to rule the sharing
// Optimize common built-in started strategies
when {
started === SharingStarted.Eagerly -> {
// collect immediately & forever
// 帮你collect,所以该启动类型是上游不等下游collect,直接运行
upstream.collect(shared)
}
started === SharingStarted.Lazily -> {
// start collecting on the first subscriber - wait for it first
// 这个是流的first{},是一个suspend函数,意义就是等待第一个订阅者(collect函数调用),再开始上游
shared.subscriptionCount.first { it > 0 }
upstream.collect(shared)
}
else -> {
// other & custom strategies
// StartedWhileSubscribed的核心逻辑,请看2
started.command(shared.subscriptionCount)
// 这里做了防抖
.distinctUntilChanged() // only changes in command have effect
.collectLatest { // cancels block on new emission
when (it) {
SharingCommand.START -> upstream.collect(shared) // can be cancelled
SharingCommand.STOP -> { /* just cancel and do nothing else */ }
SharingCommand.STOP_AND_RESET_REPLAY_CACHE -> {
if (initialValue === NO_VALUE) {
shared.resetReplayCache() // regular shared flow -> reset cache
} else {
shared.tryEmit(initialValue) // state flow -> reset to initial value
}
}
}
}
}
}
}
}
SharingStarted.command
下面的代码是StartedWhileSubscribed.command
scss
// 这里注意subscriptionCount是一个流,当对应的Flow订阅发生变化时,才会往下游emit
override fun command(subscriptionCount: StateFlow<Int>): Flow<SharingCommand> = subscriptionCount
.transformLatest { count ->
if (count > 0) {
// 订阅者数量大约0, 立即执行
emit(SharingCommand.START)
} else {
// <0, 可以停止该流的emit了
// 等待多少ms,停止
delay(stopTimeout)
// replayExpiration默认值时MAX_VALUE
if (replayExpiration > 0) {
// 立即停止
emit(SharingCommand.STOP)
// 挂起
delay(replayExpiration)
}
emit(SharingCommand.STOP_AND_RESET_REPLAY_CACHE)
}
}
// 不是start就不会往下游发了
.dropWhile { it != SharingCommand.START } // don't emit any STOP/RESET_BUFFER to start with, only START
.distinctUntilChanged() // just in case somebody forgets it, don't leak our multiple sending of START
- command 函数是控制了整个上游的开关
SharingStarted.Eagerly
和SharingStarted.Lazily
, 他们都是在没有下游collect时,不会停止的,唯一区别是开始时机- 当activity恢复的时候,
subscriptionCount
(注意这是flow)就会,重新emit,此时emit(SharingCommand.START)
就会得到执行
下面看来下游如何close的, 下游的close类似手动写job.close()
RepeatOnLifecycle.kt
kotlin
public suspend fun Lifecycle.repeatOnLifecycle(
// 1. 根据你传的state决定上游job在哪个生命周期停止和结束
state: Lifecycle.State,
block: suspend CoroutineScope.() -> Unit
) {
require(state !== Lifecycle.State.INITIALIZED) {
"repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state."
}
if (currentState === Lifecycle.State.DESTROYED) {
return
}
coroutineScope {
withContext(Dispatchers.Main.immediate) {
// Check the current state of the lifecycle as the previous check is not guaranteed
// to be done on the main thread.
if (currentState === Lifecycle.State.DESTROYED) return@withContext
// Instance of the running repeating coroutine
var launchedJob: Job? = null
// Registered observer
var observer: LifecycleEventObserver? = null
try {
// Suspend the coroutine until the lifecycle is destroyed or
// the coroutine is cancelled
suspendCancellableCoroutine<Unit> { cont ->
// startWorkEvent = ON_START, cancelWorkEvent = ON_STOP
val startWorkEvent = Lifecycle.Event.upTo(state)
val cancelWorkEvent = Lifecycle.Event.downFrom(state)
// lifecycly组件观察生命周期变化
observer = LifecycleEventObserver { _, event ->
// Activity ON_START
if (event == startWorkEvent) {
// Launch the repeating work preserving the calling context
// 重新执行block,这里的block就是外部collect {}
launchedJob = this@coroutineScope.launch(block = block)
return@LifecycleEventObserver
}
if (event == cancelWorkEvent) {
// ON_STOP job.cancel()
launchedJob?.cancel()
launchedJob = null
}
if (event == Lifecycle.Event.ON_DESTROY) {
cont.resume(Unit)
}
}
// 这里add了,在顶层lifecycleScope.launch上会remove掉
this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver)
}
} finally {
launchedJob?.cancel()
observer?.let {
this@repeatOnLifecycle.removeObserver(it)
}
}
}
}
}
java
@Nullable
public static Event upTo(@NonNull State state) {
switch (state) {
case CREATED:
return ON_CREATE;
case STARTED:
return ON_START;
case RESUMED:
return ON_RESUME;
default:
return null;
}
}
java
@Nullable
public static Event downFrom(@NonNull State state) {
switch (state) {
case CREATED:
return ON_DESTROY;
case STARTED:
return ON_STOP;
case RESUMED:
return ON_PAUSE;
default:
return null;
}
}