You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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()
            }
        }
    }
}

注意事项

  1. 线程切换:KStateMachine的onStateEntry默认在创建时指定的Dispatchers.IO执行,因此更新UI时要确保切换到主线程——Flow可以通过collect时指定Dispatchers.Main,LiveData的postValue自动处理,回调则需要手动切换(比如Android的runOnUiThread)。
  2. 内存泄漏:使用回调或Flow时,要在UI组件销毁时移除监听或取消收集,避免持有UI引用导致泄漏。
  3. 初始状态:确保暴露的状态流初始值与状态机的初始状态一致,避免UI出现不一致的初始展示。

内容的提问来源于stack exchange,提问作者DisplayName

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 12:15:24