Android学习 StateMachine与State模式
意图:
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。(Objects for States)
对象内部状态决定行为方式,对象状态改变行为方式改变;但这里为何要强调内部状态,
外部状态改变也同样会影响行为方式的改变,通常外部状态的改变都会反映到内部状态上来。
Command模式是将命令请求封装成一个为对象,将不同的请求对象参数化以达到同样的调用执行不同的命令;
同样State模式是将对象的状态封装成一个对象,是在不同的状态下同样的调用执行不同的操作。
适用性:
l 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态来改变行为;
l 一个操作中含有庞大的多分支条件语句,且这些分支依赖于该对象的状态;
根据对象的状态改变行为采用了分支语句的实现方式,解决如此多的分支语句,
将分支语句放入一个类中进行处理,即将对象的状态封装成为一个独立对象;
结构:
Context:维护一个State的子类实例,这个实例定义当前的状态;
State:定义一个接口以封装与Context的一个特定状态相关的行为;
Context与State交互:
l Context将与状态相关的请求委托给当前的ConcreteState处理;
l Context可将自身作为一个参数传递给处理该请求的状态,使得状态对象在必要的时访问Context;
l Context是客户使用的主要接口,客户可用对象来配置一个Context,一旦一个Context配置完毕,
它的客户不再需要直接与状态对象打交道;
l Context或ConcreteState子类都可以决定哪个状态是另外哪一个的后继者,以及是在何种条件下进行转换;
理解:
将对象的状态封装成一个对象,特定的状态下具体的操作行为不同,所以将对象的状态封装成一个对象目地,
是为了得到不同的行为方式;封装对象的状态是将对象的状态与行为封装在一起;可以解决庞大的分支语句带来
程序阅读性差和不便于进行扩展问题,使整个结构变得更加清晰明了,降低程序管理的复杂性提高灵活度。
解决不断根据内部变量属性改变来决定状态,这样对于复杂多状态的维护变得相对麻烦;而要保持对象的状态一致性,
使状态是作为一个对象进行改变,从Context角度来理解,状态的改变就是原子性——设置一个新的状态State对象即可,
是特定状态内部变量属性更改达到一致性。
谁定义状态转换呢? 一是由Context对象来改变,容易控制对象的状态,保持对象之间完全独立;
二是由State对象来改变设定其下一个状态,很容易控制状态改变,保证状态的连续性;由Context来改变
状态不知道何时该去改变状态比较合适,不知道当前状态处于什么情况,就需要等待当前状态执行结果然后决定;
由State对象本身来改变,则需要Context提供相应的接口来设定当前的状态,并且需要知道下一个状态对象是谁,
至少是一个状态需要被了解,造成各状态对象之间产生了很强的的依赖性,并且在下一个状态不确定的情况下,
在某种情况下才被触发,State对象本身很难去决定下一个对象是谁,你想用State来监听者些情况的触发吗?
NO不可能的;所以需要在具体问题中去权衡选择。
State对象的创建与销毁,一是当需要State对象创建它切换后销毁它;二是提前创建状态切换不销毁。
对于不同的语言和使用环境,采取的策略会不同。
下面来看一下Android平台中对于State模式的应用。
对与State改变切换这种常见常用的处理,只是各个平台框架中处理的方法不同,
这种在处理多状态较为复杂的大部分场景都能见到的策略——状态机(StateMachine)。
在Android中使用的了StateMachine机制就是一个State模式的应用, StateMachine是非常的强大和精妙。
下面先简单看一下这个StateMachine。
StateMachine类作用:
The state machine defined here is a hierarchical state machine which processes
messages and can have states arranged hierarchically.
这里的状态机是一个分层处理消息的状态机,并且是能够有分层排列状态。
下面通过这个类层次的结构来:
这样就构成了State模式:
Context——StateMachine
State ——State
StateMachine的构造函数都是protected类型,不能实例化;都是由其子类进行初始化操作;
protected StateMachine(String name) { mSmThread = new HandlerThread(name); mSmThread.start(); Looper looper = mSmThread.getLooper(); initStateMachine(name, looper); }
但是这个类具体是怎么构成上述的层次结构状态和层次消息处理仍然不明确。
下面继续看一下这个类各个模块的作用。
State:
public class State implements IState { protected State() {} public void enter() {} public void exit() {} public boolean processMessage(Message msg) {} public String getName() {} }
状态的基类,stateMachine中的状态都是由State派生而来,构造函数protected,不能实例化;
StateMachine三个内部类:
ProcessedMessageInfo: 保存已处理消息的信息
public static class ProcessedMessageInfo { private int what; //用户定义消息标识 private State state; //处理当前消息的状态 private State orgState; //消息未被处理前当前的状态 }
ProcessedMessages:
存储StateMachine最近处理的一些消息,需要保存最近处理的消息条数默认20,可以用户自己设定最大数目。
private static class ProcessedMessages { private static final int DEFAULT_SIZE = 20; private Vector<ProcessedMessageInfo> mMessages = new Vector<ProcessedMessageInfo>(); private int mMaxSize = DEFAULT_SIZE; private int mOldestIndex = 0; private int mCount = 0; }
SmHandler有三个内部类:
StateInfo:存储当前State,和其parentState,以及是否激活状态;用来构建树形层次结构模型
private class StateInfo { /** the state */ State state; /** The parent of this state, null if there is no parent */ StateInfo parentStateInfo; /** True when the state has been entered and on the stack */ boolean active; }
HaltingState与QuittingState:
都是State的 派生类,用于在状态停止和放弃之后处理的一些事情;都重写了ProcessMessage方法,
在StateMachine没有实际行动仅仅保留用于扩展。
整个SmHandle是消息处理派发和状态控制切换的核心,运行在单独的线程上。
SmHandle:
数据成员不少,列出其中关键的一些;
private static class SmHandler extends Handler { /** The current message */ private Message mMsg; /** A list of messages that this state machine has processed */ private ProcessedMessages mProcessedMessages = new ProcessedMessages(); /** Stack used to manage the current hierarchy of states */ private StateInfo mStateStack[]; /** The map of all of the states in the state machine */ private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>(); /** The initial state that will process the first message */ private State mInitialState; /** The destination state when transitionTo has been invoked */ private State mDestState; }
SmHandle是构建StateMachine的核心,运行在独立的线程上,有三个功能:
下面看看是如何完成这三个功能的:
在构成一个状态机前需要确定当前都多少状态,需要将这些状态集中起来进行管理。
StateMachine提供了这样一个protected类型方法 AddState来将状态
添加到状态机中。看看这个函数:
protected final void addState(State state, State parent) {
mSmHandler.addState(state, parent);
}实际上还是SmHandle来工作;Go on……
/**************************************************** * state: 加入state machine的State * parent: the parent of state ****************************************************/ private final StateInfo addState(State state, State parent) { StateInfo parentStateInfo = null; if (parent != null) { //获取当前状态parent详细信息 StateInfo parentStateInfo = mStateInfo.get(parent); if (parentStateInfo == null) { //当前状态父状态未加入到StateMachine中, //递归先加入其Parent State parentStateInfo = addState(parent, null); } } //判断当前状态是否加入到 StateMachine层次结构中 StateInfo stateInfo = mStateInfo.get(state); if (stateInfo == null) { //创建State详细信息对象,将其加入到StateMachine层次结构中 stateInfo = new StateInfo(); mStateInfo.put(state, stateInfo); } //验证我们没有加入相同的状态,在两个不同层次,否则异常 if ((stateInfo.parentStateInfo != null) && (stateInfo.parentStateInfo != parentStateInfo)) { throw new RuntimeException("state already added"); } //完善当前状态信息 stateInfo.state = state; stateInfo.parentStateInfo = parentStateInfo; stateInfo.active = false; return stateInfo; }
所以StateMachine类中:
StateInfo就是包装State组成一个Node,建立State的父子关系;
private HashMap<State, StateInfo> mStateInfo =
new HashMap<State, StateInfo>();
就是用来保存State Machine中的State—严格按照树形层次结构组织;
例如: SmHandle sm; sm.addState(S0,null); sm.addState(S1,S0); sm.addState(S2,S0); sm.addState(S3,S1); sm.addState(S4,S1); sm.addState(S5,S2); sm.addState(S6,S2); sm.addState(S7,S2); setInitialState(S4); //设置初始状态
得到的状态树形层次结构如下:
树形层次结构存储State就是如此完成的:
存储数据结构:StateInfo以及HashMap<State, StateInfo> mStateInfo。
方法:StateInfo addState(State state, State parent);
状态机的StateStack建立:
各状态State加入到StateMachine,各条件初始化OK后,就可以启动状态机了。
StateMachine提供了方法:
public void start() { /** Send the complete construction message */ mSmHandler.completeConstruction(); }
SmHandle:completeConstruction构建状态机运行模型
//Complete the construction of the state machine. private final void completeConstruction() { //计算State继承层次结构的最大深度以便创建运行时刻State Stack int maxDepth = 0; for (StateInfo si : mStateInfo.values()) { int depth = 0; for (StateInfo i = si; i != null; depth++) { i = i.parentStateInfo; } if (maxDepth < depth) { maxDepth = depth; } } //创建State Stack mStateStack = new StateInfo[maxDepth]; mTempStateStack = new StateInfo[maxDepth]; //根据当前mDestState(S5)按照其层次结构沿着其父子关系, //保存此条路径上的StateInfo 存储到State Stack中于是 //例如:S0--S2—S5 存储到mStateStack中 setupInitialStateStack(); //层次结构状态构建完成调用mStateStack中State的enter方法 //使mStateStack中的State 处于active状态 mIsConstructionCompleted = true; mMsg = obtainMessage(SM_INIT_CMD); invokeEnterMethods(0); //Perform any transitions requested by the enter methods performTransitions(); //待下面分析 }
这里建立State Stack是干什么用的呢?
State Stack里面的元素结构是根据父子关系组成链式结构:S0——S2——S5;S5肯定mDestState,
S2,S0都是其parentState;状态是一种父子关系,那么这两个状态之间存在某种关系;
State对应着行为,这里到底要干什么呢?
在后面我们可以看到状态行为处理执行都是根据此mStateStack进行的。
状态切换:
StateMachine中提供了方法:
protected final void transitionTo(IState destState) { mSmHandler.transitionTo(destState); }此方法用来进行状态切换;
SmHandle提供的方法:
private final void transitionTo(IState destState) { // mDestState保存当前状态 来处理消息; mDestState = (State) destState; }
而上面所提到的状态切换:protected final void transitionTo(IState destState);
仅仅是改变了当前状态mDestState,从StateStack建立这里可以看到和这个mDestState相关的还有mStateStack,
如果改变了mDestState,显然这里的mStateStack也是需要进行改变的,使mStateStack仍然是链式层次式结构。
所以上面这个状态切换其实并不算完整,还需要改变mStateStack;也就是mDestState改变时,
没有同时改变 mStateStack,而是等到消息处理派发状态Handle的时候,当前的状态行为处理完,
切换到下一个状态,即消息处理完毕然后才进行mStateStack的更新。
这个是和状态切换过程相关的:使状态切换和mStateStack的更新独立开来。
状态切换与数据处理过程是这样的:先不管谁来改变State
所以仅仅改变mDestState还不够,还需要改变mStateStack
就是这个函数:performTransitions();
先看看这样一个例子,关系还是上面的S0——S7:
mStateStack中存储:S0——S2——S5 mDestState为S5 (栈顶)
现在状态切换为S3,mDestState为S3
按照父子关系,mStateStack应该存储有:S0——S1——S3
那么此时S5,S2都要出栈pop from mStateStack
那我们就是要找到一个点,让S5,S2出栈;S3,S1进栈;
怎么去执行,这就是这个performTransitions干的事情。
主要代码如下:
//Do any transitions private synchronized void performTransitions() { while (mDestState != null) { //当前状态切换了 存在于mStateStack中的State需要改变 //仍然按照链式父子关系来存储 //先从当前状态S3找到 最近的被激活的parent状态S0 //未被激活的全部保存起来(S3,S1) 返回S0 StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); //将mStateStack中 不属于当前状态(S3), //关系链上的State(S5,S2)退出(执行exit方法) invokeExitMethods(commonStateInfo); //将S3关系链 加入到栈中(S3,S1) int stateStackEnteringIndex = moveTempStateStackToStateStack(); //将新加入到mStateStack中 未被激活的State激活(S3,S1) invokeEnterMethods(stateStackEnteringIndex); //将延迟的消息移动到消息队列的前面,以便快速得到处理 moveDeferredMessageAtFrontOfQueue(); } }
这样整个状态切换就完成了:
切换当前状态:mDestState;
更新状态栈:mStateStack;
但是当前状态的切换在StateMachine中并没有明确,因为这只是一个状态机负责状态的管理和消息派发;
谁将负责状态的切换还是交由其子类决定;
StateMachine处理的核心就是SmHandler,就是一个Handler,运行在单独线程中。
Handler是用来异步处理派发消息,这里使用Handler管理各个状态,派发消息处理到各个状态中去执行。
状态机准备OK后(状态加入和状态栈构建完成)就可以执行某些行为,接收消息进行处理,派发到当前状态去执行。
看一下SmHandler中handleMessage是如何进行消息处理的。
消息接收:
StateMachine提供了sendMessage等方法将消息加入到消息队列中,当然都是交给SmHandler去处理的。
这就关乎Handler处理消息的机制了;
消息派发:
public final void handleMessage(Message msg) { //处理当前消息到state中去处理 processMsg(msg); //消息处理完毕状态切换 更新mStateStack performTransitions(); }
private final void processMsg(Message msg) { //派发消息到state中去处理 StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; while (!curStateInfo.state.processMessage(msg)) { //当前状态mDestState 未处理该消息,交给其parent state处理 curStateInfo = curStateInfo.parentStateInfo; if (curStateInfo == null){ //此消息未被当前层次状态链处理 } } }
到这里看到建立状态栈mStateStack的作用,用来支持进行链式的消息处理;(Chain of Responsibility)
所以这是一个比较强大的状态机!
看一下Android 中WpsStateMachine对于StateMachine的应用。
WpsStateMachine作用:Manages a WPS connection。继承自StateMachine。
大致的类结构如下:
构造函数中:
WpsStateMachine(Context context, WifiStateMachine wsm, Handler target) { //初始化基类StateMachine super(TAG, target.getLooper()); //添加状态 建立树形层次结构存储 addState(mDefaultState); addState(mInactiveState, mDefaultState); addState(mActiveState, mDefaultState); //设置初始状态 setInitialState(mInactiveState); //start the state machine start(); }
其中具有的状态:DefaultState、ActiveState、InactiveState(都是WpsStateMachine内部类);
这个层次就是DefaultState作为parent,两个children:ActiveState、InactiveState;
在这些State派生类中的处理函数processMessage中能够看到transitionTo切换状态;
这里状态切换是由State完成。
后记:
这个StateMachine在什么地方使用较多,可以搜索一下代码看到有:
BluetoothAdapterStateMachine,BluetoothDeviceProfileState,
BluetoothProfileState,DataConnection,DhcpStateMachine,RilMessageDecoder,
WpsStateMachine,WifiStateMachine等等这些地方都用了到了StateMachine,结构都很相似;
这些地方都属于数据连接相关,其中状态较多,中间连接过程和处理比较复杂。
(from: http://www.cnblogs.com/bastard/archive/2012/06/05/2536258.html)
-v- 老窝 iwangyue.cn