关于StateMachine的那些事儿

1 StateMachine 简介

The state machine defined here is a hierarchical state machine which processes
messages and can have states arranged hierarchically.

这里的状态机是一个分层处理消息的状态机,并且是能够有分层排列状态。

2 StateMachine 适用范围

1. 一个对象的行为取决于它的状态,并且它必须在运行时候根据状态来改变行为;
2. 一个操作中含有庞大的多分支条件语句,且这些分支依赖于该对象的状态。

3 StateMachine 作用

1. 将状态封装成对象,在特定的状态下执行不同的操作,得到不同的行为模式;
2. 将状态和行为封装在一起,解决庞大分支语句带来的阅读性差和不方便扩展问题,降低程序的复杂性和提升灵活性。

4 StateMachine 各个模块的作用

4.1 State

public class State implements IState
{
  protected State() {}
  public void enter() {}
  public void exit() {}
  public boolean processMessage(Message msg) {}
  public String getName() {}
}

状态的基类,StateMachine中的状态都是由State派生而来。

4.2 SmHandler

**SmHandler:**SmHandler是消息处理派发和状态控制切换的核心,运行在单独的线程上。

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 mStateInfo =
    new HashMap();

  /** The initial state that will process the first message */
  private State mInitialState;
  /** The destination state when transitionTo has been invoked */
  private State mDestState;
}  

SmHandler是构建StateMachine的核心,它主要包含三个功能:

1. 建立树形结构存储State;
2. 状态机的StateStack建立和状态切换;
3. 消息的处理和派发。

4.2.1 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中没有实际行动,仅仅只是用来扩展和保留。

/**
 * State entered when transitionToHaltingState is called.
 */
private class HaltingState extends State {
    @Override
    public boolean processMessage(Message msg) {
        mSm.haltedProcessMessage(msg);
        return true;
    }
}

/**
 * State entered when a valid quit message is handled.
 */
private class QuittingState extends State {
    @Override
    public boolean processMessage(Message msg) {
        return NOT_HANDLED;
    }
}

5 StateMachine 建立树形结构存储State

在构造一个状态机的时候需要确定有多少个状态,需要将这些状态集中起来进行管理。在StateMachine中提供了protected类型方法addState来添加状态。首先看看这个方法:

/**
 * Add a new state to the state machine
 * @param state the state to add
 * @param parent the parent of state
 */
protected final void addState(State state, State parent) {
    mSmHandler.addState(state, parent);
}

实际上,还是mSmHandler来进行操作,继续深入…

/**
 * Add a new state to the state machine. Bottom up addition
 * of states is allowed but the same state may only exist
 * in one hierarchy.
 *
 * @param state the state to add
 * @param parent the parent of state
 * @return stateInfo for this 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中,
        //递归先加入其ParentState
            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 mStateInfo = new HashMap();

接下来,举个栗子来说明下:

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);      //设置初始状态

该树形层次结构的构建:

1. 存储数据结构:StateInfo、HashMap<State,StateInfo> mStateInfo;
2. 构建方法:StateInfo addState(State state, State parent)。

上面栗子构建的树形层次结构如下图:
treeState

6 StateMachine中StateStack建立和状态切换

6.1 StateMachine状态的建立

当各种状态添加到StateMachine中且各种状态初始化完毕之后,就可以启动状态机。StateMachine提供了启动状态机的方法:

public void start() {
    // mSmHandler can be null if the state machine has quit.
    SmHandler smh = mSmHandler;
    if (smh == null) return;

    /** Send the complete construction message */
    smh.completeConstruction();
}

smh.completeConstruction()这个方法用来构建状态机运行模型,继续跟进这个方法:

/**
 * Complete the construction of the state machine.
 */
private final void completeConstruction() {

    //计算State继承层次结构的最大深度以便创建运行时StateStack
    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;
        }
    }
    ...
     //创建StateStack
    mStateStack = new StateInfo[maxDepth];
    mTempStateStack = new StateInfo[maxDepth];

    //根据当前mDestState按照其层次结构沿着其父子关系,
    //保存此条路径上的StateInfo存储到StateStack中
    //例如:S0-S2-S5 存储到mStateStack中
    setupInitialStateStack();

    /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
    sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
}

提及一下,上述的StateStack是根据父子关系组成链式结构,例如S0-S2-S5;S5肯定是mDestState,而S0、S2都是其parentState,至于该StateStack是怎么进行运作的,后面继续分析。

6.2 状态切换

StateMachine中提供了方法:

protected final void transitionTo(IState destState) {
    mSmHandler.transitionTo(destState);
}

同样,还是利用mSmHandler来进行处理,继续分析:

private final void transitionTo(IState destState) {
    // mDestState保存当前状态来处理消息;
    mDestState = (State) destState;
}

从源码得知,上面提到的状态转换仅仅只是改变了当前的mDestState,其实状态改变并不完整,还需要改变mStateStack。也就是说,当mDestState状态改变的时候,没有同时改变mStateStack,而是等到消息处理派发状态处理完毕的时候做真正的状态调整,即消息处理完毕的时候才更新mStateStack。这样的操作是跟状态的过程相关,使状态的切换和mStateStack的更新独立开来。
状态切换与数据处理图示:
StateMachineState
在上面开启状态机的时候,调用sendMessageAtFrontOfQueue()这个方法,而这个方法发送消息用来改变mStateStack。

/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));

跟进这个方法:

public final void handleMessage(Message msg) {
    if (!mHasQuit) {
        /** Save the current message */
        mMsg = msg;

        /** State that processed the message */
        State msgProcessedState = null;
        if (mIsConstructionCompleted) {
            ...
        } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
                && (mMsg.obj == mSmHandlerObj)) {
            /** Initial one time path. */
            mIsConstructionCompleted = true;
            invokeEnterMethods(0);
        } else {
            ...
        }
        performTransitions(msgProcessedState, msg);
    }
}

从方法的名称我们继续跟进performTransitions(),主要代码如下:

//Do any transitions
private synchronized void performTransitions() 
{
  while (mDestState != null)
  {    
    StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
    invokeExitMethods(commonStateInfo);
    int stateStackEnteringIndex = moveTempStateStackToStateStack();
    invokeEnterMethods(stateStackEnteringIndex);      
    moveDeferredMessageAtFrontOfQueue();
  }
}

这样,状态树的切换主要包含如下几个步骤:

1. 调用transitionTo转换状态,在调用smh.transitionTo()设置目的状态,当消息处理完毕之后调用performTransitions()做真正的状态调整,主要是调整状态栈的节点信息;
2. 根据新的目的节点,调用setupTempStateStackWithStatesToEnter(),查找到根节点中没有被激活的状态,这些节点存储在mTempStateStack中,并获取新目的节点与旧目的节点之间的公共节点commonStateInfo;
3. 根据公共节点commonStateInfo,调用invokeExitMethods(),将需要移除栈的节点的状态设置为false,并且调用exit()方法;
4. 调用moveTempStateStackToStateStack()将mTempStateStack节点反转存储到mStateStack中,这样当前的目的节点置于栈顶,下次处理消息的时候直接调用当前设置的目的节点;
5. 调用invokeEnterMethods()方法,将新增加的节点状态设置为true,并调用enter()方法;
6. 在状态切换之后,如果存在消息没有处理,那么调用moveDeferredMessageAtFrontOfQueue()将延迟消息存放在消息队列的头部。

注意,当前状态的切换在StateMachine中并没有明确,因为这个只是一个状态机负责状态的管理和消息的派发,谁将负责状态的切换还是交给子类决定。

再来一个栗子,还是上面的S0-S7,若初始节点为S4,那么初始状态下,mTempStateStack中存储了S4-S1-S0,mStateStack中存储了:S0-S1-S4,那么mDestState为S4(栈顶);现在状态切换为S7,mDestState为S7;按照父子关系,mTempStateStack中存储的节点为:S7-S2-S0,接下来调用performTransitions()来进行处理:
1. 获取最新目标节点与初始目标节点的公共节点S0;
2. 根据公共节点将初始栈中除公共节点外的其它节点出栈,并且调用出栈节点的exit()方法,并修改节点状态为false;
3. 将mTempStateStack节点反转存储到mStateStack,此时mStateStack中应该存储:S0-S2-S7;
4. 激活新增节点的状态并且调用其enter()。

7 消息处理和派发

StateMachine的核心就是SmHandler,它就是一个handler,运行在单独的线程中。Handler原本是用来异步处理派发消息的,在StateMachine中使用Handler来管理各个状态,派发消息处理到各个状态中进行执行。
当状态机Ok的时候(状态加入和状态构建完成)就可以执行某些行为,接收消息并进行处理,派发到当前的状态去执行。StateMachine提供了sendMessage等方法将消息加入到消息队列,当然都是交给SmHandler去做处理,那么核心机制就是Handler的处理消息机制。看一下StateMachine中的handleMessage():

public final void handleMessage(Message msg) 
{
  //处理当前消息到state中去处理
  processMsg(msg);
  //消息处理完毕状态切换 更新mStateStack
  performTransitions();
}

核心还是processMsg()方法,继续跟进:

/**
 * Process the message. If the current state doesn't handle
 * it, call the states parent and so on. If it is never handled then
 * call the state machines unhandledMessage method.
 * @return the state that processed the message
 */
private final State processMsg(Message msg) {
    //派发消息到state中进行处理
    StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
    ...
           while (!curStateInfo.state.processMessage(msg)) {
            //当前状态mDestState未处理该消息,交给其parentState处理
            curStateInfo = curStateInfo.parentStateInfo;
            if (curStateInfo == null) {
                /**
                 * No parents left so it's not handled
                 */
                mSm.unhandledMessage(msg);
                break;
        }
    }
    return (curStateInfo != null) ? curStateInfo.state : null;
}

processMsg首先会判断当前是否退出消息,如果退出消息(isQuite(Msg)),那么就进入mQuittingState状态。

8 状态机的退出

状态机的退出,StateMachine提供了几个方法:
1. quite(): 当队列中的所有消息处理完毕之后,退出状态机;
2. quiteNow(): 不关注队列中的未处理消息,立即退出;
3. transitionToHaltingState(): 直接切换状态到悬置状态,在performTransition()方法中判断如果当前状态为mHaltingState状态,那么通过StateMachine的onHalting()的回调方法通知状态机进行退出。

你可能感兴趣的:(Android基础)