在Kotlin中使用状态机,如何向UI发送状态更新通知?
如何让UI感知KStateMachine的状态变更?
要实现UI与状态机的解耦并感知状态变化,核心思路是把状态机的状态变更事件以低耦合的方式暴露给UI层,下面是几种实用方案:
方案一:Kotlin Flow(推荐,适配协程生态)
利用Kotlin的StateFlow来持有当前状态,UI层通过收集Flow来响应状态变化,这是最贴合KStateMachine协程设计的方案。
修改StateManager代码
@Singleton class StateManager() : IStateManager { private var stateMachine: StateMachine? = null // 用MutableStateFlow维护当前状态,对外暴露只读的StateFlow private val _currentState = MutableStateFlow<State>(State.Initializing) val currentState: StateFlow<State> = _currentState.asStateFlow() init { runBlocking { startupMachine() } } private suspend fun startupMachine() { val scope = CoroutineScope(Dispatchers.IO) stateMachine = createStateMachine(scope) { addInitialState(State.Initializing) addState(State.Starting) { transition<Event.Initializing> { targetState = State.Initializing } } addState(State.Initializing) { transition<Event.Initialized> { targetState = State.Initialized } } onStateEntry { state, _ -> // 更新StateFlow _currentState.value = state println("Received new state of $state") } } } override suspend fun processEvent(event: StateEvent) { stateMachine!!.processEvent(event) } }
UI层使用示例
- Jetpack Compose场景:
// 在Composable函数中 val state by stateManager.currentState.collectAsStateWithLifecycle() when(state) { State.Initializing -> LoadingScreen() State.Initialized -> MainContent() State.Starting -> StartupAnimation() } - ViewModel/Activity场景:
// 在ViewModel中 viewModelScope.launch(Dispatchers.Main) { stateManager.currentState.collect { newState -> // 更新UI状态,比如修改LiveData或者直接操作视图 updateUI(newState) } }
方案二:回调接口(低依赖,兼容非协程场景)
通过定义回调接口,让UI层注册监听,状态机状态变化时主动通知所有注册的监听者。
步骤1:定义状态监听接口
interface StateChangeListener { fun onStateChanged(newState: State) }
步骤2:修改StateManager支持回调
@Singleton class StateManager() : IStateManager { private var stateMachine: StateMachine? = null // 维护监听者列表 private val listeners = mutableListOf<StateChangeListener>() // 注册监听 fun addStateListener(listener: StateChangeListener) { synchronized(listeners) { listeners.add(listener) } } // 移除监听 fun removeStateListener(listener: StateChangeListener) { synchronized(listeners) { listeners.remove(listener) } } init { runBlocking { startupMachine() } } private suspend fun startupMachine() { val scope = CoroutineScope(Dispatchers.IO) stateMachine = createStateMachine(scope) { // ... 原有状态定义 ... onStateEntry { state, _ -> println("Received new state of $state") // 通知所有监听者 synchronized(listeners) { listeners.forEach { it.onStateChanged(state) } } } } } // ... 其他方法 ... }
步骤3:UI层注册监听
class MainActivity : AppCompatActivity(), StateChangeListener { private lateinit var stateManager: StateManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) stateManager = // 通过DI或其他方式获取实例 stateManager.addStateListener(this) } override fun onStateChanged(newState: State) { // 在主线程更新UI runOnUiThread { when(newState) { State.Initializing -> showLoading() State.Initialized -> showMainContent() State.Starting -> showStartupAnim() } } } override fun onDestroy() { super.onDestroy() // 务必移除监听,避免内存泄漏 stateManager.removeStateListener(this) } }
方案三:LiveData(Android专属,适配生命周期)
如果是Android项目,也可以用LiveData来管理状态,它会自动感知UI生命周期,避免内存泄漏。
修改StateManager代码
@Singleton class StateManager() : IStateManager { private var stateMachine: StateMachine? = null private val _currentState = MutableLiveData<State>(State.Initializing) val currentState: LiveData<State> = _currentState init { runBlocking { startupMachine() } } private suspend fun startupMachine() { val scope = CoroutineScope(Dispatchers.IO) stateMachine = createStateMachine(scope) { // ... 原有状态定义 ... onStateEntry { state, _ -> // postValue自动切换到主线程 _currentState.postValue(state) println("Received new state of $state") } } } // ... 其他方法 ... }
UI层观察LiveData
class MainActivity : AppCompatActivity() { private lateinit var stateManager: StateManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) stateManager = // 获取实例 stateManager.currentState.observe(this) { newState -> // 自动在主线程执行,直接更新UI when(newState) { State.Initializing -> showLoading() State.Initialized -> showMainContent() State.Starting -> showStartupAnim() } } } }
注意事项
- 线程切换:KStateMachine的
onStateEntry默认在创建时指定的Dispatchers.IO执行,因此更新UI时要确保切换到主线程——Flow可以通过collect时指定Dispatchers.Main,LiveData的postValue自动处理,回调则需要手动切换(比如Android的runOnUiThread)。 - 内存泄漏:使用回调或Flow时,要在UI组件销毁时移除监听或取消收集,避免持有UI引用导致泄漏。
- 初始状态:确保暴露的状态流初始值与状态机的初始状态一致,避免UI出现不一致的初始展示。
内容的提问来源于stack exchange,提问作者DisplayName
相关产品推荐
相关产品推荐

