前言
最近在了解WIFI模块时,发现WifiController类继承至StateMachine,而WifiController是WIFI状态切换的重要类,了解StateMachine的工作流程是很有必要的。
StateMachine在状态机的类别中属于有限状态机(Finite state machine),简称FSM,属于状态设计模式中Context环境类,适用于需要在复杂状态与业务之间进行切换的场景,如游戏当中人物的走、跑、攻击,会经常在这几个状态之中进行切换,运用状态机能保证项目的可拓展性,提高可读性。
基本使用
首先声明SDK当中StateMachine的包路径为:
com.android.internal.util.StateMachine
且该类是被标注为隐藏类,我们是无法直接使用的,为了能体验下状态机的使用,我们需要将其相关类(IState、State、StateMachine)直接复制到我们的测试Demo中,当然复制完成需要稍微调整下,不然编译不通过,如下所示:
StateMachine的基本使用必须按如下四个步骤进行,缺一不可:
- 继承StateMachine,StateMachine类的构造函数是Protect访问权限,所以只能通过继承实现实例化
- 通过addState方法构造状态层次结构(树形结构,可多棵),状态层次结构根据状态转移图构建,各种状态需要继承State类,实现自己相应业务逻辑
- 通过setInitialState设置初始状态
- 调用start方法启动状态机
其他常用API如下表所示:
Method | Description |
---|---|
quit() | 停止状态机,会进入QuttingState |
sendMessage(Message msg) | 发送一个消息,供各状态处理 |
deferMessage(Message msg) | 发送一个延迟消息,在下一次状态转换时,才会被放入消息队列 |
transitionTo(IState state) | 转移至相应状态 |
transitionToHaltingState() | 进入HaltingState |
/**
* Created by graymonkey on 18-1-6.
* StateMachine类的构造函数是Protect访问权限,所以只能通过继承实现实例化
*/
public class TestStateMachine extends StateMachine {
public TestStateMachine(String name) {
super(name);
constructStatesHierarchy();
}
/**
* 构造状态层次结构(树形结构,可多棵)
*/
private void constructStatesHierarchy(){
//step 2
//构造第一棵树形层次结构
State s1 = new S1();
State s2 = new S2();
State p1 = new P1();
addState(s1,p1);
addState(s2,p1);
//构造第二棵树形层次结构
State p2 = new P2();
addState(p2);
//step 3
setInitialState(s1);
//step 4
start();
}
}
经过上面的代码,则该状态机的状态层次结构如下图所示,关于多出来的HaltingState、QuittingState见后文分析。
关于StateMachine的基本使用在SDK中首部注释中已经说的很详细了,下面直接进入源码看看其实现,同时捋一捋工作流程。
Tips:Demo需要在API24以上才能运行
Demo下载地址
源码分析—初始化准备
StateMachine构造函数
/**
* Constructor creates a StateMachine with its own thread.
*
* @param name of the state machine
*/
protected StateMachine(String name) {
mSmThread = new HandlerThread(name);
mSmThread.start();
Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
/**
* Initialize.
*
* @param looper for this state machine
* @param name of the state machine
*/
private void initStateMachine(String name, Looper looper) {
mName = name;
mSmHandler = new SmHandler(looper, this);
}
从构造函数来看,StateMachine内部开启了一个HandlerThread,并且通过SmHandler发送消息给HandlerThread。
SmHanlder
private static class SmHandler extends Handler {
......
/** Stack used to manage the current hierarchy of states */
//由mTempStateStack进行逆序反向操作得到
private StateInfo mStateStack[];
/** Top of mStateStack */
private int mStateStackTopIndex = -1;
/** A temporary stack used to manage the state stack */
private StateInfo mTempStateStack[];
/** The top of the mTempStateStack */
private int mTempStateStackCount;
/** State used when state machine is halted */
//空闲状态,当其他State都处理完毕,就会进入该状态
private HaltingState mHaltingState = new HaltingState();
/** State used when state machine is quitting */
//退出状态,停用状态机进入的状态
private QuittingState mQuittingState = new QuittingState();
/** Reference to the StateMachine */
private StateMachine mSm;
/** The map of all of the states in the state machine */
//用一个HashMap来存储状态机的状态层次结构
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的关键成员变量,SmHandler属于StateMachine的私有静态内部类,其中StateInfo、HaltingState、 QuittingState属于SmHandler的私有内部类,留意加了中文注释的成员变量,后文会用到。
SmHandler构造函数
/**
* Constructor
*
* @param looper for dispatching messages
* @param sm the hierarchical state machine
*/
private SmHandler(Looper looper, StateMachine sm) {
super(looper);
mSm = sm;
//添加空闲状态与退出状态
addState(mHaltingState, null);
addState(mQuittingState, null);
}
可见,在SmHandler实例化的时候,就会向其哈希状态表mStateInfo当中添加空闲状态与退出状态,这就是前文多出来的HaltingState、QuttingState的原因。
注意:此处addState方法并非调用的是StateMachine的addState方法,而是SmHandler自己的方法,实际上StateMachine的addState方法调用也是SmHandler的addState方法。
//StateMachine类的addState方法
/**
* Add a new state to the state machine
* @param state the state to add
* @param parent the parent of state
*/
public final void addState(State state, State parent) {
mSmHandler.addState(state, parent);
}
下面进入SmHandler#addState方法看看
/**
* 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.
*允许自下向上添加,即添加一个State可以同时添加起父状态,但是同一个
*状态不能同时拥有2个父状态,会抛出异常
*
* @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) {
if (mDbg) {
mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
+ ((parent == null) ? "" : parent.getName()));
}
StateInfo parentStateInfo = null;
if (parent != null) {
parentStateInfo = mStateInfo.get(parent);
if (parentStateInfo == null) {
// Recursively add our parent as it's not been added yet.
parentStateInfo = addState(parent, null);
}
}
StateInfo stateInfo = mStateInfo.get(state);
if (stateInfo == null) {
stateInfo = new StateInfo();
mStateInfo.put(state, stateInfo);
}
// Validate that we aren't adding the same state in two different hierarchies.
//异常校验,如果一个State已经有一个父状态,
//再添加一个父状态则会抛出异常
if ((stateInfo.parentStateInfo != null)
&& (stateInfo.parentStateInfo != parentStateInfo)) {
throw new RuntimeException("state already added");
}
//留意,StateInfo的关键数据结构,后续会用到
//当前状态
stateInfo.state = state;
//当前状态的父节点信息StateInfo类型
stateInfo.parentStateInfo = parentStateInfo;
//当前状态是否激活,调用State#enter方法后会激活置为true
stateInfo.active = false;
if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
return stateInfo;
}
该方法主要是向HashMap
注意:如果一个State已经拥有了父节点(State类型),再添加另一个新的父节点程序会抛出异常,如果需要更换父节点需要先解绑(置空)之前的父节点再添加新的父节点,错误层级结构如下图所述。
看完了addState方法,紧跟上文的基本使用步骤,我们来看看StateMachine#setInitialState方法,同addState方法一样,实际上调用也是SmHandler#setInitialState方法,直接进入SmHandler# setInitialState方法
/** @see StateMachine#setInitialState(State) */
private final void setInitialState(State initialState) {
if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
mInitialState = initialState;
}
逻辑很简单,就是对SmHandler的成员变量mInitialState进行赋值操作。
小结:至此,已经分析完基本使用步骤前三步骤的源码,这三个步骤的目的很明显就是对状态机进行一些初始化工作:
- 通过addState函数初始化状态机的状态层次结构,该层次结构由SmHandler中的HashMap
mStateInfo来存储表示。 - 通过setInitialState方法设置初始状态
源码分析—工作流程
紧跟上文基本使用步骤,我们进入第四步StateMachine#start方法,来了解状态机的工作流程。
* Start the state machine.
*/
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();
}
进入SmHandler#completeConstruction
/**
* Complete the construction of the state machine.
*/
private final void completeConstruction() {
if (mDbg) mSm.log("completeConstruction: E");
/**
* Determine the maximum depth of the state hierarchy
* so we can allocate the state stacks.
*回溯遍历所有State节点,得到最大的树形结构深度
*/
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;
}
}
if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
//初始化状态栈
mStateStack = new StateInfo[maxDepth];
mTempStateStack = new StateInfo[maxDepth];
setupInitialStateStack();
/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
if (mDbg) mSm.log("completeConstruction: X");
}
我们Demo的树状层级结构的深度应该为2,第一棵树的深度最大,下面进入setupInitialStateStack()看看是如何初始化状态栈的。
/**
* Initialize StateStack to mInitialState.
*/
private final void setupInitialStateStack() {
if (mDbg) {
mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
}
StateInfo curStateInfo = mStateInfo.get(mInitialState);
for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
mTempStateStack[mTempStateStackCount] = curStateInfo;
curStateInfo = curStateInfo.parentStateInfo;
}
//逻辑上清空mStateStack,通过将栈顶Index设置为-
mStateStackTopIndex = -1;
//初始化 mStateStack,就是将mTempStateStack逆序赋值给mStateStack
moveTempStateStackToStateStack();
}
/**
* Move the contents of the temporary stack to the state stack
* reversing the order of the items on the temporary stack as
* they are moved.
*从mStateStack的当前的栈顶index开始,添加mTempStateStack的出栈元素
* @return index into mStateStack where entering needs to start
*/
private final int moveTempStateStackToStateStack() {
int startingIndex = mStateStackTopIndex + 1;
int i = mTempStateStackCount - 1;
int j = startingIndex;
while (i >= 0) {
if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
mStateStack[j] = mTempStateStack[i];
j += 1;
i -= 1;
}
mStateStackTopIndex = j - 1;
if (mDbg) {
mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
+ ",startingIndex=" + startingIndex + ",Top="
+ mStateStack[mStateStackTopIndex].state.getName());
}
return startingIndex;
}
可见先初始化mTempStateStack,根据我们之前设置的初始化状态往上回溯直至根节点,即初始化状态(StateInfo类型)先入栈,然后是其父节点、祖父节点入栈,以此类推直到根节点入栈;接着通过moveTempStateStackToStateStack()方法初始化mStateStack,即mTempStateStack初始状态位于栈底,mStateStack初始状态位于栈顶。以我们的Demo状态层级结构为例,此时2个状态栈的内容应该如下:
接下来通过SmHandler#sendMessageAtFrontOfQueue发送SM_INIT_CMD消息,我们进入SmHandler#handleMessage方法看看是如何处理的。
/**
* Handle messages sent to the state machine by calling
* the current state's processMessage. It also handles
* the enter/exit calls and placing any deferred messages
* back onto the queue when transitioning to a new state.
*/
@Override
public final void handleMessage(Message msg) {
if (!mHasQuit) {
//如果收到的消息既不是初始化也不是退出,那么就会调用StateMachine的onPreHandleMessage方法,
//进行消息预处理
if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
mSm.onPreHandleMessage(msg);
}
if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
/** Save the current message */
mMsg = msg;
/** State that processed the message */
/**我们通过start方法首次进入肯定是走else if分支,完成初始化命令
后续收到消息才会走if分支进行消息处理*/
State msgProcessedState = null;
if (mIsConstructionCompleted) {
/** Normal path */
msgProcessedState = processMsg(msg);
} else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
&& (mMsg.obj == mSmHandlerObj)) {
/** Initial one time path. */
//设置标志位,表示初始化完成
mIsConstructionCompleted = true;
//mStateStack从栈底到栈顶,依次调用相应State的enter方法,先父亲后儿子无可厚非
//eg. P1.enter -> S1.enter
invokeEnterMethods(0);
} else {
throw new RuntimeException("StateMachine.handleMessage: "
+ "The start method not called, received msg: " + msg);
}
//处理状态转换的关键方法
performTransitions(msgProcessedState, msg);
// We need to check if mSm == null here as we could be quitting.
if (mDbg && mSm != null) mSm.log("handleMessage: X");
//如果收到的消息既不是初始化也不是退出,那么就会调用StateMachine的onPostHandleMessage方法
//表示消息处理完毕,可以转换到下一个状态
if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
mSm.onPostHandleMessage(msg);
}
}
}
/**
* 从指定起始索引stateStackEnteringIndex到栈顶,依次调用相应State的enter方法
*/
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
if (stateStackEnteringIndex == mStateStackTopIndex) {
// Last enter state for transition
mTransitionInProgress = false;
}
if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
mStateStack[i].state.enter();
mStateStack[i].active = true;
}
mTransitionInProgress = false; // ensure flag set to false if no methods called
}
StateMachine的onPreHandleMessage()和onPostHandleMessage()通过继承覆写这2个方法我们可以进行消息的预处理与结束处理,默认是空实现。
下面进入SmHandler#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) {
//获取栈顶元素,前文我们提到mStateStack的栈顶元素就是我们设置的初始状态
StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
if (mDbg) {
mSm.log("processMsg: " + curStateInfo.state.getName());
}
//如果消息是StateMachine内部发送的退出消息(调用StateMachine#quit方法),
//则切换到QuittingState
if (isQuit(msg)) {
transitionTo(mQuittingState);
} else {
while (!curStateInfo.state.processMessage(msg)) {
/**
* Not processed
*/
curStateInfo = curStateInfo.parentStateInfo;
if (curStateInfo == null) {
/**
* No parents left so it's not handled
*/
mSm.unhandledMessage(msg);
break;
}
if (mDbg) {
mSm.log("processMsg: " + curStateInfo.state.getName());
}
}
}
return (curStateInfo != null) ? curStateInfo.state : null;
}
SDK的注释说的很明白,当我们调用StateMachine#start完成初始化后,后续通过StateMachine#sendMessage发送的消息会优先分发给我们设置的初始状态进行处理,如果初始状态不能处理(State#processMessage方法返回false),则交给其父节点处理,依次类推,如果所有状态节点都无法处理,则会交给StateMachine#unhandleMessage进行处理。
看我消息处理函数,下面来看看状态转换处理函数SmHandler#performTransitions
/**
* Do any transitions
* @param msgProcessedState 是上面能处理消息的状态
*/
private void performTransitions(State msgProcessedState, Message msg) {
//省略状态机日志相关的代码
.............
//mDestState通过StateMachine#translationTo(IState state)赋值
State destState = mDestState;
if (destState != null) {
/**
* Process the transitions including transitions in the enter/exit methods
*/
while (true) {
if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
/**找到mDestState与当前的初始状态的共同祖先,并设置mTempStateStack
*如果不存在共同祖先则返回null*/
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
// flag is cleared in invokeEnterMethods before entering the target state
mTransitionInProgress = true;
//从当前初始状态到公共状态依次调用State.exit方法(不含公共状态)
//如果没有公共状态,则整个mStateStack中的State.exit都会被调用
invokeExitMethods(commonStateInfo);
//上文已经分析过这2个方法了,作用是使新的状态层级结构进入激活状态
int stateStackEnteringIndex = moveTempStateStackToStateStack();
invokeEnterMethods(stateStackEnteringIndex);
/**将延迟消息移动到前台处理队列,也就是说通过StateMachine#deferMessage()方法
*发送的消息,只有在进行下一次状态转换时才会被加到前台队列,才有机会被执行
*/
moveDeferredMessageAtFrontOfQueue();
//此处涉及并发问题,StateMachine#transitionTo并非线程安全,
//所以只要mDestState被改变则继续循环
if (destState != mDestState) {
// A new mDestState so continue looping
destState = mDestState;
} else {
// No change in mDestState so we're done
break;
}
}
mDestState = null;
}
/**
* After processing all transitions check and
* see if the last transition was to quit or halt.
*扫尾工作,可通过StateMachine#transitionToHaltingState()或
*StateMachine#quit/quitNow进入相应状态
*/
if (destState != null) {
if (destState == mQuittingState) {
/**
* 调用StateMachine的onQuittiing回调方便子类覆写处理自己的垃圾
*/
mSm.onQuitting();
cleanupAfterQuitting();
} else if (destState == mHaltingState) {
/**
* Call onHalting() if we've transitioned to the halting
* state. All subsequent messages will be processed in
* in the halting state which invokes haltedProcessMessage(msg);
*进入Halting状态,后续消息会通过HaltingState#processMessage
*回调交给StateMachine#haltedProcessMessage进行处理
*/
mSm.onHalting();
}
}
}
下面看看是如何找到公共祖先的。
private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
/**
* Search up the parent list of the destination state for an active
* state. Use a do while() loop as the destState must always be entered
* even if it is active. This can happen if we are exiting/entering
* the current state.
*/
//逻辑上清空mTempStateStack
mTempStateStackCount = 0;
StateInfo curStateInfo = mStateInfo.get(destState);
do {
mTempStateStack[mTempStateStackCount++] = curStateInfo;
curStateInfo = curStateInfo.parentStateInfo;
} while ((curStateInfo != null) && !curStateInfo.active);
if (mDbg) {
mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
+ mTempStateStackCount + ",curStateInfo: " + curStateInfo);
}
return curStateInfo;
}
逻辑很简单,先将destState节点存入mTempStateStack,然后根据destState节点往上回溯,如果该节点为非激活状态则存入mTempStateStack,直到该节点的父节点为激活状态,如果没有公共节点那么往上回溯,返回值肯定是null。
下面看看SmHandler# invokeExitMethods
/**
* Call the exit method for each state from the top of stack
* up to the common ancestor state.
*从mStateStack的栈顶依次调用State.exit方法直至公共祖先(不含公共祖先)
*/
private final void invokeExitMethods(StateInfo commonStateInfo) {
while ((mStateStackTopIndex >= 0)
&& (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
State curState = mStateStack[mStateStackTopIndex].state;
if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
curState.exit();
mStateStack[mStateStackTopIndex].active = false;
mStateStackTopIndex -= 1;
}
}
该方法就是从初始状态往上回溯调用exit方法,直至公共祖先(不含公共祖先)。
至此StateMachine工作流程相关的代码都已经分析完成。
总结
假设我们经过前三步基本使用步骤构造的状态层次结构图如下所示:
当我们调用start()方法,进而会调用SmHandler#completeConstruction(),该方法首先会初始化2个状态栈
接着发送一个SM_INIT_CMD消息,当SmHandler#handleMessage(),处理这个初始化消息时,会调用SmHandler#invokeEnterMethods(0),依次从mStateStack的栈底(因为传入参数为0)到栈顶调用对应State.enter()方法,即enter方法的调用顺序为P0->P1->S2->S5,并将State.active设置为true,表示已经激活。
当SmHandler处理通过StateMachine#sendMessage()发送的消息时,会调用SmHandler的processMsg()方法,消息分发逻辑如下:
消息会优先分配给当前的初始状态,如果该状态不能处理该消息(State#processMessage返回false),则分发给其父节点,以此类推,如果所有状态都不能处理,则分发给StateMachine的unhandleMessage方法进行处理,即消息会从mStateStack的栈顶分发至栈底。
处理完消息会调用SmHandler#performTransitions方法,进行状态转移,假设我们调用StateMachine#transitionTo(S4),设置S4为目的状态,performTransitions的主要工作逻辑如下:
- 调用SmHandler#setupTempStateStackWithStatesToEnter方法找到目的状态与当前初始状态S5(mStateStack的栈顶元素)的公共祖先即P1,同时对mTempStateStack进行重新赋值:先将目的状态S4入栈,然后根据S4往上回溯,如果节点未被激活则入栈,直到找到一个处于激活状态的节点,该节点即是目的状态与当前初始状态的公共祖先。此时,mTempStateStack逻辑上的结构应当如下图所示。
- 接着调用SmHandler#invokeExitMethods(commonStateInfo)方法,退出旧的状态,mStateStack依次出栈调用State.exit()方法,直到公共祖先P1(不含公共祖先),即exit的调用顺序为S5->S2,此时mStateStack逻辑上结构如下图所示。
- 将mTempStateStack整合至mStateStack
- 调用SmHandler# invokeEnterMethods方法,从公共节点之上依次调用State.enter方法,直到栈顶,即新状态的enter调用顺序为S1->S4。