配置变更后SharedFlow事件未被收集的问题求助
场景描述
采用SharedViewModel方案实现Activity与Fragment交互,通过SharedFlow在Activity中共享并更新UI状态,状态变更由Fragment触发。正常使用Jetpack Navigation切换Fragment时功能正常,但调用AppCompatDelegate.setDefaultNightMode切换日夜主题触发配置变更后,Activity的收集器无法接收Fragment发出的事件。
调试确认流程:
- 调用
AppCompatDelegate.setDefaultNightMode触发配置变更 - Activity执行
collectShared(mainActivityState, ::onActivityStateChanged) - Fragment调用
mainSharedViewModel.setSubTitle()
但Activity的onActivityStateChanged未被调用。通过按钮触发setSubTitle()或添加延迟后,收集器能正常接收事件,存在明显竞态条件。
现有代码实现
宿主Activity
val viewModel: MainSharedViewModel by viewModels() // onCreate 方法内 with(viewModel) { collectShared(mainActivityState, ::onActivityStateChanged) } private fun onActivityStateChanged(state: MainActivityState) { // 监听当前对用户可见的Fragment if (state is MainActivityState.ToolbarTitleChanged) { toolBar.subtitle = state.subTitle } }
扩展函数
inline fun <T : Any, L : SharedFlow<T>> LifecycleOwner.collectShared( sharedFlow: L, crossinline function: (T) -> Unit, lifecycleState: Lifecycle.State = Lifecycle.State.STARTED ) { lifecycleScope.launch { repeatOnLifecycle(lifecycleState) { sharedFlow.collect { t -> function(t) } } } }
Fragment
private val mainSharedViewModel: MainSharedViewModel by activityViewModels() // onCreateView 方法内 mainSharedViewModel.setSubTitle() // 传入默认空值
SharedViewModel
private val _mainActivityState = MutableSharedFlow<MainActivityState>() val mainActivityState = _mainActivityState.asSharedFlow() fun setSubTitle(subtitle: String = DEFAULT_VALUE_STRING) { viewModelScope.launch { _mainActivityState.emit(MainActivityState.ToolbarTitleChanged(subtitle)) } }
原因分析
问题核心在于SharedFlow的默认特性:
- 默认
replay=0、extraBufferCapacity=0,意味着当事件被emit时,如果没有活跃的收集器订阅,事件会直接丢失。 - 配置变更后,Activity与Fragment会重建,但Fragment的
onCreateView执行时机可能早于Activity中repeatOnLifecycle启动收集器的时机。此时Fragment调用setSubTitle发出事件,Activity的收集器尚未处于活跃状态,导致事件丢失。
按钮触发或添加延迟能正常接收,是因为此时Activity的收集器已经完成订阅并处于活跃状态,事件能被正常消费。
最优解决方案
方案1:改用StateFlow(推荐)
StateFlow是专门为UI状态同步设计的流,默认replay=1,会始终保存最新的状态值。当收集器启动时,会立即收到当前最新的状态,完全适配这种UI状态同步场景。
修改SharedViewModel代码:
// 初始化时设置默认状态 private val _mainActivityState = MutableStateFlow<MainActivityState>( MainActivityState.ToolbarTitleChanged(DEFAULT_VALUE_STRING) ) val mainActivityState = _mainActivityState.asStateFlow() fun setSubTitle(subtitle: String = DEFAULT_VALUE_STRING) { // StateFlow直接赋值,无需协程 _mainActivityState.value = MainActivityState.ToolbarTitleChanged(subtitle) }
Activity的收集逻辑无需修改,collectShared函数可以兼容StateFlow(因为StateFlow是SharedFlow的子类)。
方案2:调整SharedFlow参数(兼容原有逻辑)
如果坚持使用SharedFlow,可通过设置replay=1让流保存最近1次事件,确保新收集器订阅时能收到最新事件:
private val _mainActivityState = MutableSharedFlow<MainActivityState>(replay = 1)
但此方案仅解决事件丢失问题,相比StateFlow,SharedFlow更适合一次性事件场景,而Toolbar标题属于需要持久化的UI状态,StateFlow是更贴合场景的选择。
方案3:优化事件发送时机(不推荐)
通过延迟Fragment的事件发送,确保Activity收集器已启动:
// Fragment中修改调用时机 viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { mainSharedViewModel.setSubTitle() } }
此方案依赖生命周期时机,可靠性低,仅作为临时兼容方案,不推荐长期使用。
内容的提问来源于stack exchange,提问作者Bitwise DEVS

