前言
当我们的系统需要对状态进管理时,我们很容易就写出以下代码:
import java.util.concurrent.atomic.AtomicReference
//状态
object Idle : StateMachine.State()
object Initialized : StateMachine.State()
object Prepared : StateMachine.State()
object Preparing : StateMachine.State()
object Started : StateMachine.State()
object Paused : StateMachine.State()
object Stopped : StateMachine.State()
object PlaybackCompleted : StateMachine.State()
object End : StateMachine.State()
object Error : StateMachine.State()
/*
* 有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。、
*/
class StateMachine private constructor(
val name: String = "StateMachine",//状态机名称
private val initialState: State//初始状态
) {
/**
* 使用Atomic支持多线程
*/
private val stateRef = AtomicReference(initialState)
/**
* 当前状态,定义为val不给外部直接修改
*/
val state: State
get() = stateRef.get()
/**
* 状态转换
* @param state STATE
*/
fun transition(state: State) {
stateRef.set(state)
}
/**
* 状态
*/
sealed class State
companion object {
/**
* 创建一个状态机
* @param name String 状态机名称
* @param initialState State 初始状态
* @return StateMachine 状态机实例
*/
fun create(
name: String = "StateMachine",
initialState: State
): StateMachine {
return StateMachine(name, initialState)
}
}
}
这个代码你就说能不能用吧?能用是能用,但是有问题!首先,这就不是一个完整的状态机,其次...咱后面再说。
什么是状态机?
状态机一般指有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机(英语:finite-state automaton,缩写:FSA),是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。
状态机有由4个组成部分:状态(State)、事件(Event)、转移(Transition),动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只发生状态的转移,而不执行任何动作。
状态(State) :将一个系统离散化,可以得到很多种状态,当然这些状态是有限的。例如:门禁闸机可以划分为开启状态、关闭状态;电扇可以划分为关、一档、二档、三档等状态。
转移(Transition) :一个状态接收一个输入执行了某些动作到达了另外一个状态的过程就是一个transition(转移)。定义transition(转移)就是在定义状态机的转移流程。
事件(Event) :也叫做转移条件(Transition Condition),在某一状态下,只有达到了transition condition(转移条件),才会按照状态机的转移流程转移到下一状态,并执行相应的动作。
动作(Action):在状态机的运转过程中会有很多种动作。如:进入动作(entry action)[在进入状态时进行]、退出动作(exit action)[在退出状态时进行]、转移动作[在进行特定转移时进行]。
![](https://i-blog.csdnimg.cn/blog_migrate/5412dd7d475a7c7fa0920aa5130b7854.png)
实现状态机的方法有多种,比较常用的有分支法、查表法、状态模式。
明白了状态机的定义,再回过头看上面的代码,很容易就发现这个代码缺少事件(Event)、转移(Transition),动作(Action)这几个部分,不是一个完整的状态机。
想想实际使用这段代码时会遇到什么问题?
状态来源不明确;
状态的转移条件不明确;
无法在状态转移流程中执行动作;
无法监听状态的改变;
没有线程调度。
接下来,我们按照实现状态机的三种方法来重构上面的代码。
状态机实现-分支法
它的核心思想是根据状态迁移图,要么先确定状态、要么先确定事件,直译代码。
方法分析:对于简单状态机,此方法是可以接受的。但是,对于复杂的状态机,这种实现极易漏写或错写某个状态转移;代码中充斥大量if-else或switch-case 分支判断逻辑,可读性和可维护性差。
以Android MediaPlayer为例,它的状态迁移图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/2212f9b4ae6a842827bfc6029eec760b.gif)
使用分支逻辑法修改后的代码如下,处理逻辑是先确定事件,然后在事件内根据状态进行状态转移。
import kotlinx.coroutines.*
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicReference
/**
* 定义MediaPlayer接口
*/
interface IMediaPlayer {
fun reset()
fun setDateResource()
fun prepare()
fun prepareAsync()
fun start()
fun pause()
fun stop()
fun release()
fun setOnPreparedListener(onPreparedListener: OnPreparedListener)
fun setOnErrorListener(onErrorListener: OnErrorListener)
}
interface OnPreparedListener {
fun onPrepared(errorCode: Int)
}
interface OnErrorListener {
fun onError(errorCode: Int)
}
/**
* MediaPlayer状态
*/
sealed class MediaPlayerState {
object Idle : MediaPlayerState()
object Initialized : MediaPlayerState()
object Prepared : MediaPlayerState()
object Preparing : MediaPlayerState()
object Started : MediaPlayerState()
object Paused : MediaPlayerState()
object Stopped : MediaPlayerState()
object PlaybackCompleted : MediaPlayerState()
object End : MediaPlayerState()
object Error : MediaPlayerState()
}
/**
* MediaPlayer事件
*/
sealed class MediaPlayerEvent {
object Reset : MediaPlayerEvent()
object Init : MediaPlayerEvent()
object Prepare : MediaPlayerEvent()
object PrepareAsync : MediaPlayerEvent()
object Start : MediaPlayerEvent()
object Pause : MediaPlayerEvent()
object Stop : MediaPlayerEvent()
object Complete : MediaPlayerEvent()
object Release : MediaPlayerEvent()
object OnError : MediaPlayerEvent()
}
/**
* 使用分支逻辑法状态机的MediaPlayer演示代码
* @property stateMachine StateMachineBranch<MediaPlayerState, MediaPlayerEvent>
* @constructor
*/
class MediaPlayerBranch : IMediaPlayer {
private val stateMachine: StateMachineBranch<MediaPlayerState, MediaPlayerEvent> =
StateMachineBranch.create(
"MediaPlayerBranch",
MediaPlayerState.Idle,
Executors.newSingleThreadExecutor { runnable ->
Thread(runnable, "Dispatcher-MediaPlayerBranch")
}.asCoroutineDispatcher(),// 协程调度器
::onEvent,
::onTransition
)
/**
* 当前状态
* @return MediaPlayerState
*/
val state: MediaPlayerState
get() = stateMachine.state
/**
* 事件处理
* @param event MediaPlayerEvent
*/
private fun onEvent(event: MediaPlayerEvent) {
when (event) {
MediaPlayerEvent.Reset -> {
stateMachine.transition(MediaPlayerState.Idle)
}
MediaPlayerEvent.Init -> {
if (stateMachine.state == MediaPlayerState.Idle) {
stateMachine.transition(MediaPlayerState.Initialized)
}
}
MediaPlayerEvent.Pause -> {
if (stateMachine.state == MediaPlayerState.Started) {
stateMachine.transition(MediaPlayerState.Paused)
}
}
MediaPlayerEvent.Prepare -> {
if (stateMachine.state == MediaPlayerState.Initialized) {
stateMachine.transition(MediaPlayerState.Prepared)
}
}
MediaPlayerEvent.PrepareAsync -> {
if (stateMachine.state == MediaPlayerState.Initialized) {
stateMachine.transition(MediaPlayerState.Preparing)
}
}
MediaPlayerEvent.Start -> {
if (stateMachine.state == MediaPlayerState.Prepared) {
stateMachine.transition(MediaPlayerState.Started)
}
}
MediaPlayerEvent.Stop -> {
if (stateMachine.state == MediaPlayerState.Prepared || stateMachine.state == MediaPlayerState.Started) {
stateMachine.transition(MediaPlayerState.Stopped)
}
}
MediaPlayerEvent.Complete -> {
if (stateMachine.state == MediaPlayerState.Started) {
stateMachine.transition(MediaPlayerState.PlaybackCompleted)
}
}
MediaPlayerEvent.Release -> {
stateMachine.transition(MediaPlayerState.End)
}
MediaPlayerEvent.OnError -> {
stateMachine.transition(MediaPlayerState.Error)
}
}
}
/**
* 监听状态转移
* @param state MediaPlayerState
*/
private fun onTransition(state: MediaPlayerState) {
when (state) {
MediaPlayerState.End -> {}
MediaPlayerState.Error -> {}
MediaPlayerState.Idle -> {}
MediaPlayerState.Initialized -> {}
MediaPlayerState.Paused -> {}
MediaPlayerState.PlaybackCompleted -> {}
MediaPlayerState.Prepared -> {}
MediaPlayerState.Preparing -> {}
MediaPlayerState.Started -> {}
MediaPlayerState.Stopped -> {}
}
}
override fun reset() {
stateMachine.sendEvent(MediaPlayerEvent.Reset)
}
override fun setDateResource() {
stateMachine.sendEvent(MediaPlayerEvent.Init)
}
override fun prepare() {
stateMachine.sendEvent(MediaPlayerEvent.Prepare)
}
override fun prepareAsync() {
stateMachine.sendEvent(MediaPlayerEvent.PrepareAsync)
}
override fun start() {
stateMachine.sendEvent(MediaPlayerEvent.Start)
}
override fun pause() {
stateMachine.sendEvent(MediaPlayerEvent.Pause)
}
override fun stop() {
stateMachine.sendEvent(MediaPlayerEvent.Stop)
}
override fun release() {
stateMachine.sendEvent(MediaPlayerEvent.Release)
}
override fun setOnPreparedListener(onPreparedListener: OnPreparedListener) {
//TODO
}
override fun setOnErrorListener(onErrorListener: OnErrorListener) {
//TODO
}
}
/*
* 有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
* 分支法实现
*/
class StateMachineBranch<STATE : Any, EVENT : Any> private constructor(
private val name: String = "StateMachineBranch",//状态机名称
initialState: STATE,//初始状态,
coroutineDispatcher: CoroutineDispatcher = Executors.newSingleThreadExecutor { runnable ->
Thread(runnable, "Dispatcher-${name}")
}.asCoroutineDispatcher(),// 协程调度器
private val onEvent: (EVENT) -> Unit,//事件处理,
private val onTransition: (STATE) -> Unit,//监听状态转移
) {
/**
* 协程作用域
*/
private val coroutineScope: CoroutineScope =
CoroutineScope(coroutineDispatcher + SupervisorJob())
/**
* 使用Atomic支持多线程
*/
private val stateRef = AtomicReference(initialState)
/**
* 当前状态,定义为val不给外部直接修改
*/
val state: STATE
get() = stateRef.get()
/**
* 阻塞型事件发送
* @param event EVENT
*/
fun sendEvent(event: EVENT) {
onEvent(event)
}
/**
* 非阻塞型事件发送
* @param event EVENT
*/
fun postEvent(event: EVENT) {
coroutineScope.launch {
sendEvent(event)
}
}
/**
* 状态转换
* @param state STATE
*/
fun transition(state: STATE) {
coroutineScope.launch {
stateRef.set(state)
onTransition(state)
}
}
companion object {
/**
* 创建一个状态机
* @param name String 状态机名称
* @param initialState STATE 初始状态
* @param onEvent Function1<EVENT, Unit> 事件处理
* @param onTransition Function1<STATE, Unit> 监听状态转移
* @return StateMachineBranch<STATE, EVENT> 状态机实例
*/
fun <STATE : Any, EVENT : Any> create(
name: String = "StateMachineBranch",
initialState: STATE,
coroutineDispatcher: CoroutineDispatcher = Executors.newSingleThreadExecutor { runnable ->
Thread(runnable, "Dispatcher-${name}")
}.asCoroutineDispatcher(),// 协程调度器
onEvent: (EVENT) -> Unit,
onTransition: (STATE) -> Unit
): StateMachineBranch<STATE, EVENT> {
return StateMachineBranch(
name,
initialState,
coroutineDispatcher,
onEvent,
onTransition
)
}
}
}
单元测试:
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun testStateMachineBranch() {
val mediaPlayerBranch = MediaPlayerBranch(UnconfinedTestDispatcher()).apply {
setDateResource()
}
assertEquals(State.Initialized, mediaPlayerBranch.state)
mediaPlayerBranch.prepare()
assertEquals(State.Prepared, mediaPlayerBranch.state)
}
}
以上代码实现了一个完整的状态机。初始代码中存在的以下的问题也都被解决。
状态来源不明确;
优化:分支法使用了sendEvent函数来替代setState函数,每个状态(State)的转移(Transition)都有唯一指定的事件(Event),这样一来我们我们只需要关注事件(Event)的来源即可。
状态转移条件不明确;
优化:分支法将事件(Event)回调给调用方的onEvent函数处理,并且在做状态(State)的转移(Transition)时增加了转移条件(Transition Condition)的判断,可有解决免状态(State)迁移条件不明确的问题。
无法在状态转移流程中执行动作;
无法监听状态的改变;
优化:分支法新增了onTransition函数回调在transition函数之后。按照笔者的理解,动作(Action)可以理解为Hook状态机某个点后执行的代码。而分支法Hook的点就是在transition函数之后,在这里我们可以执行的动作(Action)既可以是监听状态的改变,也可以是其它的业务逻辑,因此笔者把第三第四点合并到一起理解。理论上,我们可以将状态机运转过程中任何我们需要Hook的点暴露出来给使用者,这样调使用者就可以随心所欲的处理。总之,代码是死的,人是活的,我们应该根据实际的业务需要来设计一个满足我们使用的状态机。
可能没接触过 “Hook”名词的同学,看这段文字会感觉不太好理解。Hook直译是钩子,钩子是什么意思,如果把程序比作生产流水线,那么Hook就像是钩子钩住了在传输带上传输的物品,这样钩子携带的目标的就可以随着被钩住的物品走完剩余的生产流程。简单理解就是,Hook可以将我们的代码埋入程序执行过程中的某个点,这样我们的代码就可以这点被执行后跟着被执行。
没有线程调度。
优化:按笔者的理解,基于事件的驱动的系统,都应该有线程调度,像Android有Handler绑定指定的线程;像LiveData设计了setValue(),postValue()两个方法来更新Value,setValue()只能在主线程中调用:多次调用每次都会收到,而postValue()可以在任何线程中调用,但连续调用,只会收到最后一条更新(当然是在上一条没有发送之前,又收到一条消息时,前一条会被覆盖)。
分支法增加了CoroutineScope来实现线程调度,提供默认的CoroutineDispatcher并可以自定义,这样一来就可以在指定的线程发送事件(Event)并处理事件(Event)。
还有其它问题吗?当然还有。比如分支法大量使用if-else或switch-case 进行分支判断,因此极容易出现漏写错写问题。但得益于Kotlin密封类的特性,使用when可以避免这个问题,编译器会提示我们还有哪些分支没有被处理。
还有呢?还有蛮多问题的,但受限于笔者的知识储备和不同的业务实际需求不一样,就不继续展开了。欢迎大家提出批评和建议。
状态机实现-查表法
状态机除了用状态转移图表示外,还可以用二维表表示。第一维表示当前状态,第二维表示事件,值表示当前状态接收到事件之后,转移到的新状态以及其执行的动作。
先回顾MediaPlayer状态转移图中包含的状态和事件。
/**
* MediaPlayer状态
*/
sealed class MediaPlayerState {
object Idle : MediaPlayerState()
object Initialized : MediaPlayerState()
object Prepared : MediaPlayerState()
object Preparing : MediaPlayerState()
object Started : MediaPlayerState()
object Paused : MediaPlayerState()
object Stopped : MediaPlayerState()
object PlaybackCompleted : MediaPlayerState()
object End : MediaPlayerState()
object Error : MediaPlayerState()
}
/**
* MediaPlayer事件
*/
sealed class MediaPlayerEvent {
object Reset : MediaPlayerEvent()
object Init : MediaPlayerEvent()
object Prepare : MediaPlayerEvent()
object PrepareAsync : MediaPlayerEvent()
object Start : MediaPlayerEvent()
object Pause : MediaPlayerEvent()
object Stop : MediaPlayerEvent()
object Complete : MediaPlayerEvent()
object Release : MediaPlayerEvent()
object OnError : MediaPlayerEvent()
}
转换成MediaPlayer播放器状态迁移表:
![](https://i-blog.csdnimg.cn/blog_migrate/738caabe01995ec3b5004b5890eac69e.png)
由于编辑器问题,这里只能截图展示。相比于分支法的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,只需要修改transitionTable和actionTable两个二维数组即可。
查表法的示例代码如下:
import kotlinx.coroutines.*
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicReference
/**
* 使用查表法状态机的MediaPlayer演示代码
* @property stateMachine StateMachineTable
* @constructor
*/
class MediaPlayerTable(
coroutineDispatcher: CoroutineDispatcher = Executors.newSingleThreadExecutor { runnable ->
Thread(runnable, "Dispatcher-MediaPlayerTable")
}.asCoroutineDispatcher()
) : IMediaPlayer {
/**
* 状态
* @constructor
*/
sealed class State : ITableIndex {
object Idle : State() {
override fun getIndex() = 0
}
object Initialized : State() {
override fun getIndex() = 1
}
object Prepared : State() {
override fun getIndex() = 2
}
object Preparing : State() {
override fun getIndex() = 3
}
object Started : State() {
override fun getIndex() = 4
}
object Paused : State() {
override fun getIndex() = 5
}
object Stopped : State() {
override fun getIndex() = 6
}
object Completed : State() {
override fun getIndex() = 7
}
object End : State() {
override fun getIndex() = 8
}
object Error : State() {
override fun getIndex() = 9
}
}
/**
* 事件
* @constructor
*/
sealed class Event: ITableIndex {
object Reset : Event(){
override fun getIndex(): Int =0
}
object Init : Event(){
override fun getIndex(): Int =1
}
object Prepare : Event(){
override fun getIndex(): Int =2
}
object PrepareAsync : Event(){
override fun getIndex(): Int =3
}
object Start : Event(){
override fun getIndex(): Int =4
}
object Pause : Event(){
override fun getIndex(): Int =5
}
object Stop : Event(){
override fun getIndex(): Int =6
}
object Complete : Event(){
override fun getIndex(): Int =7
}
object Release : Event(){
override fun getIndex(): Int =8
}
object OnError : Event(){
override fun getIndex(): Int =9
}
}
private val transitionTable: Array<Array<State?>> = arrayOf(
/** Reset(0), Init(1), Prepare(2), PrepareAsync(3),Start(4), Pause(5), Stop(6), Complete(7), Release(8), OnError(9);**/
/**Idle**/ arrayOf(null , State.Initialized, null , null ,null ,null ,null ,null , State.End, State.Error),
/**Initialized**/arrayOf(State.Idle, null , State.Prepared, State.Preparing,null ,null ,null ,null , State.End, State.Error),
/**Prepared**/ arrayOf(State.Idle, null , null , null ,State.Started,null ,State.Stopped,null , State.End, State.Error),
/**Preparing**/ arrayOf(State.Idle, null , State.Prepared, null ,null ,null ,null ,null , State.End, State.Error),
/**Started**/ arrayOf(State.Idle, null , null , null ,null ,State.Paused,State.Stopped,null , State.End, State.Error),
/**Paused**/ arrayOf(State.Idle, null , null , null ,State.Started,null ,State.Stopped,State.Completed, State.End, State.Error),
/**Stopped**/ arrayOf(State.Idle, null , null , null ,null ,null ,null ,null , State.End, State.Error),
/**Completed**/ arrayOf(State.Idle, null , null , null ,null ,null ,State.Stopped,null , State.End, State.Error),
/**End**/ arrayOf(State.Idle, null , null , null ,null ,null ,null ,null , State.End, State.Error),
/**Error**/ arrayOf(State.Idle, null , null , null ,null ,null ,null ,null , State.End, State.Error),
)
sealed class Action:IAction {
object ResetAction : Action() {
override fun doAction() {
}
}
object InitAction : Action() {
override fun doAction() {
}
}
object PrepareAction : Action() {
override fun doAction() {
}
}
object PrepareAsyncAction : Action() {
override fun doAction() {
}
}
object StartAction : Action() {
override fun doAction() {
}
}
object PauseAction : Action() {
override fun doAction() {
}
}
object StopAction : Action() {
override fun doAction() {
}
}
object CompleteAction : Action() {
override fun doAction() {
}
}
object EndAction : Action() {
override fun doAction() {
}
}
object ErrorAction : Action() {
override fun doAction() {
}
}
}
private val actionTable: Array<Array<IAction?>> = arrayOf(
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
arrayOf(Action.ResetAction, Action.InitAction, Action.PrepareAction, Action.PrepareAsyncAction, Action.StartAction, Action.PauseAction, Action.StopAction, Action.CompleteAction, Action.EndAction, Action.ErrorAction),
)
private val stateMachine: StateMachineTable<State,Event> = StateMachineTable.create(
State.Idle,
coroutineDispatcher,
transitionTable,
actionTable
)
/**
* 当前状态
* @return MediaPlayerState
*/
val state: State
get() = stateMachine.state
override fun reset() {
stateMachine.sendEvent(Event.Reset)
}
override fun setDateResource() {
stateMachine.sendEvent(Event.Init)
}
override fun prepare() {
stateMachine.sendEvent(Event.Prepare)
}
override fun prepareAsync() {
stateMachine.sendEvent(Event.PrepareAsync)
}
override fun start() {
stateMachine.sendEvent(Event.Start)
}
override fun pause() {
stateMachine.sendEvent(Event.Pause)
}
override fun stop() {
stateMachine.sendEvent(Event.Stop)
}
override fun release() {
stateMachine.sendEvent(Event.Release)
}
override fun setOnPreparedListener(onPreparedListener: OnPreparedListener) {
TODO("Not yet implemented")
}
override fun setOnErrorListener(onErrorListener: OnErrorListener) {
TODO("Not yet implemented")
}
}
/**
* 状态机接口
*/
interface IStateMachineTable<STATE : Any,EVENT : Any> {
/**
* 状态机根据事件转移状态
* @param event EVENT
*/
fun sendEvent(event: EVENT)
/**
* 状态机转移状态
* @param state STATE
*/
fun transition(state: STATE)
}
/**
* 查表法下标
*/
interface ITableIndex {
fun getIndex():Int
}
/**
* 指定的动作
*/
interface IAction{
fun doAction()
}
/*
* 有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
* 查表法实现
*/
class StateMachineTable<STATE : ITableIndex, EVENT : ITableIndex> private constructor(
initialState: STATE,//初始状态,
coroutineDispatcher: CoroutineDispatcher,// 协程调度器
private val transitionTable:Array<Array<STATE?>>,
private val actionTable : Array<Array<IAction?>>
): IStateMachineTable<STATE,EVENT>{
/**
* 协程作用域
*/
private val coroutineScope: CoroutineScope =
CoroutineScope(coroutineDispatcher + SupervisorJob())
/**
* 使用Atomic支持多线程
*/
private val stateRef = AtomicReference(initialState)
/**
* 当前状态,定义为val不给外部直接修改
*/
val state: STATE
get() = stateRef.get()
/**
* 状态机根据事件转移状态
* @param event EVENT
*/
override fun sendEvent(event: EVENT) {
coroutineScope.launch {
val stateIndex: Int = state.getIndex()
val eventIndex: Int = event.getIndex()
//查表,根据状态和事件所在下标获取到下一个状态
transitionTable[stateIndex][eventIndex]?.let {
transition(it)
}
//查表根据根据状态和事件所在下标获取到需要执行的动作
actionTable[stateIndex][eventIndex]?.let {
it.doAction()
}
}
}
/**
* 状态机转移状态
* @param state STATE
*/
override fun transition(state: STATE) {
coroutineScope.launch {
stateRef.set(state)
}
}
companion object{
fun <STATE : ITableIndex, EVENT : ITableIndex> create(
initialState: STATE,//初始状态,
coroutineDispatcher: CoroutineDispatcher,// 协程调度器
transitionTable: Array<Array<STATE?>>,
actionTable : Array<Array<IAction?>>
): StateMachineTable<STATE,EVENT> {
return StateMachineTable(
initialState,
coroutineDispatcher,
transitionTable,
actionTable
)
}
}
}
单元测试代码:
import com.lonbon.statemachine.blog.*
import kotlinx.coroutines.test.*
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun testStateMachineTable() {
val mediaPlayerTable = MediaPlayerTable(UnconfinedTestDispatcher()).apply {
setDateResource()
}
assertEquals(MediaPlayerTable.State.Initialized, mediaPlayerTable.state)
mediaPlayerTable.prepare()
assertEquals(MediaPlayerTable.State.Prepared, mediaPlayerTable.state)
}
}
可以看到,查表法通过二维数组的下标可以很轻松完成状态的转移和动作的执行,结构非常清晰,维护也很方便,不过也有不足之处,那就是不方便应对复杂的状态流转逻辑,因为状态,事件,动作是一一对应的。假若我们的业务逻辑非常复杂,例如电商的秒杀流程,要经过风控,鉴权,价格优惠计算,队列,库存扣除,支付等一系流程才能完成,那么查表法这种一一对应的设计就让代码结构变得混乱难以维护。这时候就到状态模式大显身手了。
状态机实现-状态模式
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的Context 对象。
状态模式主要解决控制一个对象状态的条件表达式过于复杂的问题,它把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。对应到代码层面,就相当于消除了 if-else或switch-case的弊端。
以下是状态模式原理类图说明,明确状态模式的角色及职责.
![](https://i-blog.csdnimg.cn/blog_migrate/4d1ad0ec53ea9061dc6fb2470e4ebdb3.png)
IStateMachine是状态机抽象角色,提供了发送Event和转移State的接口;
StateMachineStatePattern是IStateMachine的实现,维护了IState实现类的实例;
IState 是状态抽象角色,定义一个接口用于接收处理Event并调用IStateMachine的接口转移State;
ConcreteState是IState的具体实现类,我们通过创建它的对象来处理Event并触发状态转移;
Event是事件抽象角色,ConcreteEvent是Event的具体实现类,不同的State只能由具体的Event来触发转移。
使用状态模式实现的状态机代码示例如下:
import kotlinx.coroutines.*
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicReference
/**
* 使用状态模式状态机的MediaPlayer演示代码
* @property stateMachine StateMachineStatePattern<MediaPlayerState, MediaPlayerEvent>
* @constructor
*/
class MediaPlayerStatePattern : IMediaPlayer {
private val stateMachine: StateMachineStatePattern<MediaPlayerEvent> =
StateMachineStatePattern.create(
IdleState, Executors.newSingleThreadExecutor { runnable ->
Thread(runnable, "Dispatcher-MediaPlayerStatePattern")
}.asCoroutineDispatcher(),
::onTransition
)
/**
* 当前状态
* @return MediaPlayerState
*/
val state: IState<MediaPlayerEvent>
get() = stateMachine.state
/**
* 监听状态转移
* @param state MediaPlayerState
*/
private fun onTransition(state: IState<MediaPlayerEvent>) {
when (state) {
IdleState -> {
}
InitializedState -> {
}
PreparedState -> {
}
PreparingState -> {
}
StartedState -> {
}
PausedState -> {
}
StoppedState -> {
}
EndState -> {
}
ErrorState -> {
}
}
}
override fun reset() {
stateMachine.sendEvent(MediaPlayerEvent.Reset)
}
override fun setDateResource() {
stateMachine.sendEvent(MediaPlayerEvent.Init)
}
override fun prepare() {
stateMachine.sendEvent(MediaPlayerEvent.Prepare)
}
override fun prepareAsync() {
stateMachine.sendEvent(MediaPlayerEvent.PrepareAsync)
}
override fun start() {
stateMachine.sendEvent(MediaPlayerEvent.Start)
}
override fun pause() {
stateMachine.sendEvent(MediaPlayerEvent.Pause)
}
override fun stop() {
stateMachine.sendEvent(MediaPlayerEvent.Stop)
}
override fun release() {
stateMachine.sendEvent(MediaPlayerEvent.Release)
}
override fun setOnPreparedListener(onPreparedListener: OnPreparedListener) {
//TODO
}
override fun setOnErrorListener(onErrorListener: OnErrorListener) {
//TODO
}
}
/**
* 状态机接口
*/
interface IStateMachine<EVENT : Any> {
/**
* 状态机根据事件转移状态
* @param event EVENT
*/
fun sendEvent(event: EVENT)
/**
* 状态机转移状态
* @param state EVENT
*/
fun transition(state: IState<EVENT>)
}
/**
* 状态接口
*/
interface IState<EVENT : Any> {
/**
* 状态实例调用此方法转移状态
* @param stateMachine IStateMachine<EVENT>
* @param event EVENT
*/
fun transition(stateMachine: IStateMachine<EVENT>, event: EVENT)
}
//初始状态
object IdleState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
//当前是IdleState状态,如果接收到Init事件,则转移到InitializedState状态
if (event == MediaPlayerEvent.Init) {
stateMachine.transition(InitializedState)
}
if (event == MediaPlayerEvent.Release) {
stateMachine.transition(EndState)
}
if (event == MediaPlayerEvent.OnError) {
stateMachine.transition(ErrorState)
}
}
}
object InitializedState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
if (event == MediaPlayerEvent.Prepare) {
stateMachine.transition(PreparedState)
}
if (event == MediaPlayerEvent.PrepareAsync) {
stateMachine.transition(PreparingState)
}
if (event == MediaPlayerEvent.Release) {
stateMachine.transition(EndState)
}
if (event == MediaPlayerEvent.OnError) {
stateMachine.transition(ErrorState)
}
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
object PreparedState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
if (event == MediaPlayerEvent.Start) {
stateMachine.transition(StartedState)
}
if (event == MediaPlayerEvent.Release) {
stateMachine.transition(EndState)
}
if (event == MediaPlayerEvent.OnError) {
stateMachine.transition(ErrorState)
}
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
object PreparingState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
if (event == MediaPlayerEvent.Prepare) {
stateMachine.transition(PreparedState)
}
if (event == MediaPlayerEvent.Release) {
stateMachine.transition(EndState)
}
if (event == MediaPlayerEvent.OnError) {
stateMachine.transition(ErrorState)
}
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
object StartedState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
if (event == MediaPlayerEvent.Stop) {
stateMachine.transition(StoppedState)
}
if (event == MediaPlayerEvent.Pause) {
stateMachine.transition(PausedState)
}
if (event == MediaPlayerEvent.Complete) {
stateMachine.transition(PlaybackCompletedState)
}
if (event == MediaPlayerEvent.Release) {
stateMachine.transition(EndState)
}
if (event == MediaPlayerEvent.OnError) {
stateMachine.transition(ErrorState)
}
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
object PausedState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
if (event == MediaPlayerEvent.Start) {
stateMachine.transition(StartedState)
}
if (event == MediaPlayerEvent.Stop) {
stateMachine.transition(StoppedState)
}
if (event == MediaPlayerEvent.Release) {
stateMachine.transition(EndState)
}
if (event == MediaPlayerEvent.OnError) {
stateMachine.transition(ErrorState)
}
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
object StoppedState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
if (event == MediaPlayerEvent.Prepare) {
stateMachine.transition(PreparedState)
}
if (event == MediaPlayerEvent.PrepareAsync) {
stateMachine.transition(PreparingState)
}
if (event == MediaPlayerEvent.Release) {
stateMachine.transition(EndState)
}
if (event == MediaPlayerEvent.OnError) {
stateMachine.transition(ErrorState)
}
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
object PlaybackCompletedState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
if (event == MediaPlayerEvent.Stop) {
stateMachine.transition(StoppedState)
}
if (event == MediaPlayerEvent.Release) {
stateMachine.transition(EndState)
}
if (event == MediaPlayerEvent.OnError) {
stateMachine.transition(ErrorState)
}
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
object EndState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
//EndState只能通过Reset事件返回IdleState
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
object ErrorState : IState<MediaPlayerEvent> {
override fun transition(
stateMachine: IStateMachine<MediaPlayerEvent>,
event: MediaPlayerEvent
) {
//ErrorState只能通过Reset事件返回IdleState
if (event == MediaPlayerEvent.Reset) {
stateMachine.transition(IdleState)
}
}
}
/*
* 有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
* 状态模式实现
*/
class StateMachineStatePattern<EVENT : Any> private constructor(
initialState: IState<EVENT>,//初始状态
coroutineDispatcher: CoroutineDispatcher,// 协程调度器
private val onTransition: (IState<EVENT>) -> Unit,//监听状态转移
) : IStateMachine<EVENT> {
/**
* 协程作用域
*/
private val coroutineScope: CoroutineScope =
CoroutineScope(coroutineDispatcher + SupervisorJob())
/**
* 使用Atomic支持多线程
*/
private val stateRef = AtomicReference(initialState)
/**
* 当前状态,定义为val不给外部直接修改
*/
val state: IState<EVENT>
get() = stateRef.get()
/**
* 阻塞型事件发送
* @param event EVENT
*/
override fun sendEvent(event: EVENT) {
state.transition(this, event)
}
/**
* 非阻塞型事件发送
* @param event EVENT
*/
fun postEvent(event: EVENT) {
coroutineScope.launch {
sendEvent(event)
}
}
override fun transition(state: IState<EVENT>) {
coroutineScope.launch {
stateRef.set(state)
onTransition(state)
}
}
companion object {
/**
* 创建一个状态机
* @param initialState IState<EVENT> 初始状态
* @param coroutineDispatcher CoroutineDispatcher 状态机实例
* @param onTransition Function1<IState<EVENT>, Unit> 监听状态迁移
* @return StateMachineStatePattern<EVENT>
*/
fun <EVENT : Any> create(
initialState: IState<EVENT>,//初始状态
coroutineDispatcher: CoroutineDispatcher,// 协程调度器
onTransition: (IState<EVENT>) -> Unit
): StateMachineStatePattern<EVENT> {
return StateMachineStatePattern(initialState, coroutineDispatcher, onTransition)
}
}
}
单元测试:
class ExampleUnitTest {
@Test
fun testStateMachineStatePattern() {
val mediaPlayerStatePattern = MediaPlayerStatePattern(UnconfinedTestDispatcher()).apply {
setDateResource()
}
assertEquals(InitializedState, mediaPlayerStatePattern.state)
mediaPlayerStatePattern.prepare()
assertEquals(PreparedState, mediaPlayerStatePattern.state)
}
}
可以看到每个State的具体实现类,都可以单独处理自己的逻辑,这样符合单一职责原则。但如果新增状态,不仅需要增加一个新的State实现类,还需要修改其它的State来增加转移到新增State的逻辑,这样的话,其实对于“开闭原则”支持的不是很好,而且State类无法利用到密封类的特性,存在漏写状态转移逻辑的可能。
另外,我们可以看到如果状态越多的,State的实现类越多,导致类膨胀,结构上会变得混乱起来,相比分支法和查表法,状态模式的实现不是那容易让我们通过代码理清状态迁移的转移条件。
看看分支法,得益于密封类特性,我们不会漏处理事件,并且很容易看出是什么事件导致从什么状态下迁移到什么状态。
/**
* 事件处理
* @param event MediaPlayerEvent
*/
private fun onEvent(event: MediaPlayerEvent) {
when (event) {
MediaPlayerEvent.Reset -> {
stateMachine.transition(MediaPlayerState.Idle)
}
MediaPlayerEvent.Init -> {
if (stateMachine.state == MediaPlayerState.Idle) {
stateMachine.transition(MediaPlayerState.Initialized)
}
}
MediaPlayerEvent.Pause -> {
if (stateMachine.state == MediaPlayerState.Started) {
stateMachine.transition(MediaPlayerState.Paused)
}
}
MediaPlayerEvent.Prepare -> {
if (stateMachine.state == MediaPlayerState.Initialized) {
stateMachine.transition(MediaPlayerState.Prepared)
}
}
MediaPlayerEvent.PrepareAsync -> {
if (stateMachine.state == MediaPlayerState.Initialized) {
stateMachine.transition(MediaPlayerState.Preparing)
}
}
MediaPlayerEvent.Start -> {
if (stateMachine.state == MediaPlayerState.Prepared) {
stateMachine.transition(MediaPlayerState.Started)
}
}
MediaPlayerEvent.Stop -> {
if (stateMachine.state == MediaPlayerState.Prepared || stateMachine.state == MediaPlayerState.Started) {
stateMachine.transition(MediaPlayerState.Stopped)
}
}
MediaPlayerEvent.Complete -> {
if (stateMachine.state == MediaPlayerState.Started) {
stateMachine.transition(MediaPlayerState.PlaybackCompleted)
}
}
MediaPlayerEvent.Release -> {
stateMachine.transition(MediaPlayerState.End)
}
MediaPlayerEvent.OnError -> {
stateMachine.transition(MediaPlayerState.Error)
}
}
}
再看看查表法,二维数组也是很清晰的:
private val transitionTable: Array<Array<State?>> = arrayOf(
/** Reset(0), Init(1), Prepare(2), PrepareAsync(3),Start(4), Pause(5), Stop(6), Complete(7), Release(8), OnError(9);**/
/**Idle**/ arrayOf(null , State.Initialized, null , null ,null ,null ,null ,null , State.End, State.Error),
/**Initialized**/arrayOf(State.Idle, null , State.Prepared, State.Preparing,null ,null ,null ,null , State.End, State.Error),
/**Prepared**/ arrayOf(State.Idle, null , null , null ,State.Started,null ,State.Stopped,null , State.End, State.Error),
/**Preparing**/ arrayOf(State.Idle, null , State.Prepared, null ,null ,null ,null ,null , State.End, State.Error),
/**Started**/ arrayOf(State.Idle, null , null , null ,null ,State.Paused,State.Stopped,null , State.End, State.Error),
/**Paused**/ arrayOf(State.Idle, null , null , null ,State.Started,null ,State.Stopped,State.Completed, State.End, State.Error),
/**Stopped**/ arrayOf(State.Idle, null , null , null ,null ,null ,null ,null , State.End, State.Error),
/**Completed**/ arrayOf(State.Idle, null , null , null ,null ,null ,State.Stopped,null , State.End, State.Error),
/**End**/ arrayOf(State.Idle, null , null , null ,null ,null ,null ,null , State.End, State.Error),
/**Error**/ arrayOf(State.Idle, null , null , null ,null ,null ,null ,null , State.End, State.Error),
)
不过其实也不是大的问题,合适的才是最好的。状态模式的强项在于可以将复杂的状态流转逻辑拆分成一个个单一的职责的状态类来处理,但是不能够好很好的应对状态流转逻辑频繁变动的情况。
小结
最后,我们从可维护性,可扩展性,可复用性这三个角度来比较这三种状态机的实现。
可维护性(Maintainability)
反映在软件中纠正一个缺陷或做出一个更改的简易程度。代码完成之后,如果要修改部分功能,需要修改的地方很少,就是可维护性高。
分支法:相当于代码直译(有面向过程那种感觉),通常是先判断事件再判断状态,不同的事件和状态,一般都是使用if-else或switch-case 语句,比较容易漏写错写,而且分支越多越复杂,大量的if-else或switch-case判断,甚至是多层嵌套的if-else或switch-case,实在让人头疼,以至于网络上出现了很多文章教你如何消除if-else,什么提前return呀,策略模式等等。
if-else虽然有些讨厌,不过利用Kotlin的密封类特性,可以得到一定的改善。
笔者打分:可维护指数:★☆☆
查表法:查表法用二维表表示。第一维表示当前状态,第二维表示事件,值表示当前状态接收到事件之后,转移到的新状态以及其执行的动作。想一想,在日常生活中,当我们遇到一个复杂的问题时,常常率会有这样的思维:列个表格不就清楚了吗?搞这么复杂干嘛?!是的,表格就是具有这样的特性,能够清晰的展现状态迁移,事件处理的路径。在代码中使用二维数组就可以制作一张状态表,根据二维数组的下标可以直接定位到目标状态和动作。
笔者打分:可维护指数:★★★
状态模式:状态模式是一种设计模式。这不,设计模式一套,瞬间高大上。状态模式的魅力正在于可以把复杂的状态判断逻辑转移到表示不同状态的一系列类中,从而简化逻辑。对应到代码层面,就相当于消除了 if-else或switch-case的弊端。你看,继承,封装,多态,面向对象编程那味就出来了。
笔者打分:可维护指数:★★★
可扩展性(Scalablity)
反映软件适应“变化”的能力。代码完成之后,要在原来的基础上增加新功能,只需要添加该功能的代码,不需要修改原来的代码(对之前的代码没有影响),这就是可扩展性高。
分支法:都用上if-else或switch-case 语句了,还谈扩展性那就是耍流氓,新增一个状态就得新增一个分支,不改源码都不行。
笔者打分:可扩展指数:★☆☆
查表法:新增一个状态也需要修改transitionTable和actionTable两个二维数组,这样看来新增功能的可扩展性也不高,不过好在要定位修改的位置会比较简单。如果说要执行的动作也很简单,我们甚至可以把二维数组写到配置中,通过读写配置来完成扩展和修改。
笔者打分:可扩展指数:★★☆
状态模式:添加新的状态很简单,增加新的状态实例即可,不需要修改使用它们的Context对象,不过涉及到旧状态转移到新状态仍然需要修改其它状态的代码,因此也不能说可扩展性很高,不过这是大家都有的问题,是吧,这样一比较是不是好了很多?
笔者打分:可扩展指数:★★★
可复用性(Reusability)
反映软件能够被重复使用的难易程度。代码完成之后,以后开发中可以复用部分代码,提高效率,就是可复用性强。
复用性这个角度不太切入,即便是同一种实现方式,可能不同的人写出来的代码差别还是会很大。
分支法:分支法的惯用写法,容易把逻辑都写一个类中,如果代码很多,那真的是又臭又长,不利于复用。但是我么回过头看分支法的代码,它有一个优点就是封装的状态机类中State和Event都可以是泛型,这样我们复用这个状态机,就可以完全使用不同的State和Event,即便状态机给不同的系统使用都是可以满足的,从这个角度来说,可复用性它是高的。再谈到Action,其实也完全可以提供接口给外部单独处理,再结合Kotlin密封类,那其实也是很好复用的。个人觉得分支法如果稍微重新设计下也还是奴不错的。
笔者打分:可扩展指数:★★☆
查表法:查表法按照笔者的理解,这种一一对应的设计还是太看使用场景了,如果想要复用的话拆分表是很难的,业务稍微不同,表的结构都得变动,回过头看看代码中TableIndex的设计,再想想数组和链表的优缺点,就知道数组不适合增删改,只适合查。
笔者打分:可扩展指数:★☆☆
状态模式:状态模式其实和策略模式是有点像的,正因为复杂逻辑被拆分到具体的实现类,所以复用状态类会非常简单。不过回过头看状态机的代码,由于Context对象要执行IState的抽象方法用于转移状态,因此这里要求具体的State必须实现固定的接口或父类,从而导致状态机无法使用泛型,这样一来如果不同的系统要复用状态机代码就不太行了,容易导致State被污染。除非直接复制一份代码修改,不过这样就不是真正的复用了。
笔者打分:可扩展指数:★★☆
文字太多了,归纳成表格方便大家对比:
维度\实现 | 分支法 | 查表法 | 状态模式 |
可维护性 | ★☆☆ | ★★★ | ★★★ |
可扩展性 | ★☆☆ | ★★☆ | ★★★ |
可复用性 | ★☆☆ | ★☆☆ | ★★☆ |
虽然从可维护性,可扩展性,可复用性这三个角度比较了这三种状态机实现的优缺点,但抛开使用场景谈优缺点都是不客观的,合适的才是最好的,不同的状态机有它适合的使用场景。
建议使用场景 | |
分支法 | 适用于状态不多,不频繁增加状态的简单系统。比如,控制电灯的开关。 |
查表法 | 适用于状态多但执行动作简单的系统。比如一些小游戏,主角有普通,变身两种状态,不同的状态攻击力不一样,那么状态转移后指定的动作仅仅是修改下攻击力数值即可。 |
状态模式 | 适用于状态转移逻辑复杂但状态变动不频繁的系统。如订单系统,下单流程,要经过风控,鉴权,价格优惠计算,队列,库存扣除,支付等一系流程才能完成。 |
写在最后,首先非常感谢您耐心阅读完整篇文章,坚持写原创且基于实战的文章不是件容易的事,如果本文刚好对您有点帮助,欢迎您给文章点赞评论,您的鼓励是笔者坚持不懈的动力。写博客不仅仅是巩固学习的一个好方式,更是一个观点碰撞查漏补缺的绝佳机会,若文章有不对之处非常欢迎指正,再次感谢。
参考资料
Android官方的MediaPlayer
菜鸟教程的状态模式
美团技术团队设计模式二三事