1.在说android的状态机之前,我们先说一下java的状态模式:
状态模式,又称状态对象模式,状态模式是对象的行为模式。
状态模式允许一个对象在其内部状态改变的时候改变其行为。这个对象看上去就像是改变了它的类一样。
状态模式所涉及到的角色有:
● 环境(Context)角色,也成上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
● 抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
● 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。
使用场景
考虑一个在线投票系统的应用,要实现控制同一个用户只能投一票,如果一个用户反复投票,而且投票次数超过5次,则判定为恶意刷票,要取消该用户投票的资格,当然同时也要取消他所投的票;如果一个用户的投票次数超过8次,将进入黑名单,禁止再登录和使用系统。
要使用状态模式实现,首先需要把投票过程的各种状态定义出来,根据以上描述大致分为四种状态:正常投票、反复投票、恶意刷票、进入黑名单。然后创建一个投票管理对象(相当于Context)。
系统的结构图如下所示:
源代码
抽象状态类
public interface VoteState { /** * 处理状态对应的行为 * @param user 投票人 * @param voteItem 投票项 * @param voteManager 投票上下文,用来在实现状态对应的功能处理的时候, * 可以回调上下文的数据 */ public void vote(String user,String voteItem,VoteManager voteManager); }
具体状态类——正常投票
public class NormalVoteState implements VoteState { @Override public void vote(String user, String voteItem, VoteManager voteManager) { //正常投票,记录到投票记录中 voteManager.getMapVote().put(user, voteItem); System.out.println("恭喜投票成功"); } }
具体状态类——重复投票
public class RepeatVoteState implements VoteState { @Override public void vote(String user, String voteItem, VoteManager voteManager) { //重复投票,暂时不做处理 System.out.println("请不要重复投票"); } }
具体状态类——恶意刷票
public class SpiteVoteState implements VoteState { @Override public void vote(String user, String voteItem, VoteManager voteManager) { // 恶意投票,取消用户的投票资格,并取消投票记录 String str = voteManager.getMapVote().get(user); if(str != null){ voteManager.getMapVote().remove(user); } System.out.println("你有恶意刷屏行为,取消投票资格"); } }
具体状态类——黑名单
public class BlackVoteState implements VoteState { @Override public void vote(String user, String voteItem, VoteManager voteManager) { //记录黑名单中,禁止登录系统 System.out.println("进入黑名单,将禁止登录和使用本系统"); } }
环境类
public class VoteManager { //持有状体处理对象 private VoteState state = null; //记录用户投票的结果,Map<String,String>对应Map<用户名称,投票的选项> private Map<String,String> mapVote = new HashMap<String,String>(); //记录用户投票次数,Map<String,Integer>对应Map<用户名称,投票的次数> private Map<String,Integer> mapVoteCount = new HashMap<String,Integer>(); /** * 获取用户投票结果的Map */ public Map<String, String> getMapVote() { return mapVote; } /** * 投票 * @param user 投票人 * @param voteItem 投票的选项 */ public void vote(String user,String voteItem){ //1.为该用户增加投票次数 //从记录中取出该用户已有的投票次数 Integer oldVoteCount = mapVoteCount.get(user); if(oldVoteCount == null){ oldVoteCount = 0; } oldVoteCount += 1; mapVoteCount.put(user, oldVoteCount); //2.判断该用户的投票类型,就相当于判断对应的状态 //到底是正常投票、重复投票、恶意投票还是上黑名单的状态 if(oldVoteCount == 1){ state = new NormalVoteState(); } else if(oldVoteCount > 1 && oldVoteCount < 5){ state = new RepeatVoteState(); } else if(oldVoteCount >= 5 && oldVoteCount <8){ state = new SpiteVoteState(); } else if(oldVoteCount > 8){ state = new BlackVoteState(); } //然后转调状态对象来进行相应的操作 state.vote(user, voteItem, this); } }
客户端类
public class Client { public static void main(String[] args) { VoteManager vm = new VoteManager(); for(int i=0;i<9;i++){ vm.vote("u1","A"); } } }
运行结果如下:
从上面的示例可以看出,状态的转换基本上都是内部行为,主要在状态模式内部来维护。比如对于投票的人员,任何时候他的操作都是投票,但是投票管理对象的处理却不一定一样,会根据投票的次数来判断状态,然后根据状态去选择不同的处理。
认识状态模式
● 状态和行为
所谓对象的状态,通常指的就是对象实例的属性的值;而行为指的就是对象的功能,再具体点说,行为大多可以对应到方法上。
状态模式的功能就是分离状态的行为,通过维护状态的变化,来调用不同状态对应的不同功能。也就是说,状态和行为是相关联的,它们的关系可以描述为:状态决定行为。
由于状态是在运行期被改变的,因此行为也会在运行期根据状态的改变而改变。
● 行为的平行性
注意平行线而不是平等性。所谓平行性指的是各个状态的行为所处的层次是一样的,相互独立的、没有关联的,是根据不同的状态来决定到底走平行线的哪一条。行为是不同的,当然对应的实现也是不同的,相互之间是不可替换的。
而平等性强调的是可替换性,大家是同一行为的不同描述或实现,因此在同一个行为发生的时候,可以根据条件挑选任意一个实现来进行相应的处理。
大家可能会发现状态模式的结构和策略模式的结构完全一样,但是,它们的目的、实现、本质却是完全不一样的。还有行为之间的特性也是状态模式和策略模式一个很重要的区别,状态模式的行为是平行性的,不可相互替换的;而策略模式的行为是平等性的,是可以相互替换的。
● 环境和状态处理对象
在状态模式中,环境(Context)是持有状态的对象,但是环境(Context)自身并不处理跟状态相关的行为,而是把处理状态的功能委托给了状态对应的状态处理类来处理。
在具体的状态处理类中经常需要获取环境(Context)自身的数据,甚至在必要的时候会回调环境(Context)的方法,因此,通常将环境(Context)自身当作一个参数传递给具体的状态处理类。
客户端一般只和环境(Context)交互。客户端可以用状态对象来配置一个环境(Context),一旦配置完毕,就不再需要和状态对象打交道了。客户端通常不负责运行期间状态的维护,也不负责决定后续到底使用哪一个具体的状态处理对象。
我们来看一下Android的状态机:
对与State改变切换这种常见常用的处理,只是各个平台框架中处理的方法不同,
这种在处理多状态较为复杂的大部分场景都能见到的策略——状态机(StateMachine)。
在Android中使用的了StateMachine机制就是一个State模式的应用, StateMachine是非常的强大和精妙。
下面先简单看一下这个StateMachine。
StateMachine类作用:
这里的状态机是一个分层处理消息的状态机,并且是能够有分层排列状态。
下面通过这个类层次的结构来:
这样就构成了State模式:
Context——StateMachine
State ——State
StateMachine的构造函数都是protected类型,不能实例化;都是由其子类进行初始化操作;
protected StateMachine(String name)
{
mSmThread = new HandlerThread(name);
mSmThread.start();
Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
但是这个类具体是怎么构成上述的层次结构状态和层次消息处理仍然不明确。
下面继续看一下这个类各个模块的作用。
1 State Machine各个模块作用
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的核心,运行在独立的线程上,有三个功能:
- 建立树形层次结构存储State;
- 状态机的StateStack建立和状态切换;
- 消息处理和派发;
下面看看是如何完成这三个功能的:
2 建立树形层次结构存储State
在构成一个状态机前需要确定当前都多少状态,需要将这些状态集中起来进行管理。
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);
3 状态机的StateStack建立和状态切换
状态机的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中并没有明确,因为这只是一个状态机负责状态的管理和消息派发;
谁将负责状态的切换还是交由其子类决定;
4 消息处理和派发
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)
所以这是一个比较强大的状态机!