在上一篇了解到了StateMachine
状态机的使用,这通过源码来分析一下原理。
-
先来看一下StateMachine的构造方法
protected StateMachine(String name) { mSmThread = new HandlerThread(name); mSmThread.start(); Looper looper = mSmThread.getLooper(); initStateMachine(name, looper); } private void initStateMachine(String name, Looper looper){ mName = name; mSmHandler = new SmHandler(looper, this); }
在构造方法里头创建了个
HandlerThread
对象,因此它有自己的工作线程。在initStateMachine()
中初始化了状态机的重要角色SmHandler
。private SmHandler(Looper looper, StateMachine sm) { super(looper); mSm = sm; addState(mHaltingState, null); addState(mQuittingState, null); }
在
SmHandler
构造方法中,添加了两个状态,这两个状态分别是停止状态和退出状态。这两个状态跟例子中的一样,同样是继承于State
这个类。private class HaltingState extends State { @Override public boolean processMessage(Message msg) { mSm.haltedProcessMessage(msg); return true; } } private class QuittingState extends State { @Override public boolean processMessage(Message msg) { return NOT_HANDLED; } }
继续看一下
State
类以及接口IState
public interface IState { void enter(); void exit(); boolean processMessage(Message msg); String getName(); } public class State implements IState { protected State() {} @Override public void enter() {} @Override public void exit() {} @Override public boolean processMessage(Message msg) { return false; } @Override public String getName() { String name = getClass().getName(); int lastDollar = name.lastIndexOf('$'); return name.substring(lastDollar + 1); } }
接口定义了四个函数,分别代表进入该状态的回调,退出该状态的回调,处理消息事件,获取状态名称。
初始化这部分介绍完,跟
StateMachine
相关的所有类也有所了解,接下来依次分析addState(State)
、setInitialState(State)
、start()
、sendMessage(Message)
、transitionTo(State)
。 -
addState(State)
添加状态状态机通过
addState
方法将所有状态保存起来。// 只复制了核心代码 // 保存所有的状态 private HashMap
mStateInfo = new HashMap (); private final StateInfo addState(State state, State parent) { StateInfo parentStateInfo = null; // 在状态树上找不到parent状态,则将parent至为null if (parent != null) { parentStateInfo = mStateInfo.get(parent); if (parentStateInfo == null) { parentStateInfo = addState(parent, null); } } StateInfo stateInfo = mStateInfo.get(state); if (stateInfo == null) { stateInfo = new StateInfo(); mStateInfo.put(state, stateInfo); } // 避免有多个parent状态 if ((stateInfo.parentStateInfo != null) && (stateInfo.parentStateInfo != parentStateInfo)) { throw new RuntimeException("state already added"); } stateInfo.state = state; stateInfo.parentStateInfo = parentStateInfo; stateInfo.active = false; return stateInfo; } 状态机是通过一个Map去收集状态的,并且需要确保parent是已经被提前添加进去的,而且一个状态最多只能有一个parent状态。
-
setInitialState(State)
设置初始状态private final void setInitialState(State initialState) { mInitialState = initialState; }
实现很简单,只是将初始状态赋值给
mInitialState
。 -
start()
启动状态机public void start() { SmHandler smh = mSmHandler; if (smh == null) return; smh.completeConstruction(); } private final void completeConstruction() { int maxDepth = 0; // 1 for (StateInfo si : mStateInfo.values()) { int depth = 0; for (StateInfo i = si; i != null; depth++) { i = i.parentStateInfo; } if (maxDepth < depth) { maxDepth = depth; } } // 2 mStateStack = new StateInfo[maxDepth]; mTempStateStack = new StateInfo[maxDepth]; // 3 setupInitialStateStack(); // 4 sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)); }
真正的启动是在
completeConstruction()
实现的:-
首先计算所有节点中的最大深度:
- 遍历每个节点,通过父子关系查找根节点;
- 累计起始节点到根节点的节点数,查找最大节点数;
根据最大深度构造两个数组
mStateStack
与mTempStateStack
;-
调用
setupInitialStateStack()
填充栈mStateStack
与mTempStateStack
;private final void setupInitialStateStack() { // setInitialState()设置的初始状态在这被使用 // 获取初始状态的状态信息 StateInfo curStateInfo = mStateInfo.get(mInitialState); // 从当前状态->根状态,依次入栈 for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) { mTempStateStack[mTempStateStackCount] = curStateInfo; curStateInfo = curStateInfo.parentStateInfo; } mStateStackTopIndex = -1; moveTempStateStackToStateStack(); }
这部分主要是遍历初始状态的状态树,然后依次添加到
mTempStateStack
中,入栈顺序是”子在底,父在上“,如下:index state x Px . . . . . . 2 P1 1 P0 0 S <-- mInitialState
填充完
mTempStateStack
之后,通过函数moveTempStateStackToStateStack()
去填充mStateStack
。private final int moveTempStateStackToStateStack() { // setupInitialStateStack()将mStateStackTopIndex设置为-1 // startingIndex从0开始 int startingIndex = mStateStackTopIndex + 1; // i从末尾开始 // j从0开始 int i = mTempStateStackCount - 1; int j = startingIndex; while (i >= 0) { mStateStack[j] = mTempStateStack[i]; j += 1; i -= 1; } mStateStackTopIndex = j - 1; return startingIndex; }
从代码中可以看到
mStateStack
中状态的顺序正好是mTempStateStack
的倒序,即:”父在底,子在上“,如下:index state x S <-- mInitialState x-1 P0 . . . . . . 2 P(x-2) 1 P(x-1) 0 PX
-
状态栈填充完成之后,将
SM_INIT_CMD
消息插入到消息队列的头部并发送。那就去看看SmHandler:handleMessage(msg)
是如何处理该消息的。// 删除了无关代码 public final void handleMessage(Message msg) { if (!mHasQuit) { // 状态机未退出 mMsg = msg; State msgProcessedState = null; if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) { // 初始化完成之后 msgProcessedState = processMsg(msg); } else if (!mIsConstructionCompleted && (mMsg.what ==SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) { // 初始化未完成,处理SM_INIT_CMD消息 mIsConstructionCompleted = true; invokeEnterMethods(0); } performTransitions(msgProcessedState, msg); } }
从代码注释上可以清楚了解到:
- 接收到
SM_INIT_CMD
消息会去执行invokeEnterMethods(0)
函数,并将mIsConstructionCompleted
赋值为true
,认为初始化已经完成; - 初始化完成之后接收到的消息会在
process(msg)
中处理,也就是说上一篇中的例子发送的CMD_X
消息会在这被处理; - 最后还会执行函数
performTransitions(msgProcessedState, msg)
,执行此命令的时候,改函数里头没做什么工作,所以这个函数暂时不去分析。
品一下函数
invokeEnterMethods(0)
完成了些什么工作:private final void invokeEnterMethods(int stateStackEnteringIndex) { for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { if (stateStackEnteringIndex == mStateStackTopIndex) { mTransitionInProgress = false; } mStateStack[i].state.enter(); mStateStack[i].active = true; } mTransitionInProgress = false; }
遍历了状态栈
mStateStack
,在上面已经说过,mStateStack
的顺序是“子在上,父在底”,stateStackEnteringIndex == 0
,所以是从根状态开始依次调用enter()
函数,并激活状态。这也解释了例子中P1的enter()
先与S1
。 - 接收到
-
总结
到这整个状态机的启动工作已经做完了,虽然有点长,但是也就做了两件事:
- 将
mInitialState
--> 根状态填充到mState
中,并且按照“子在上,父在底”的顺序入栈; - 将刚刚填充好的
mState
从父到子(从下到上)依次执行enter()
函数,并激活状态。
- 将
-
-
通过
SendMessage(Message)
来了解状态机对消息的处理发送消息其实没什么好讲的,重点看一下消息处理,消息处理的代码已经在分析
start()
的时候贴出来了,对于非SM_INIT_CMD
消息,会执行processMsg(msg)
函数。private final State processMsg(Message msg) { // 获取当前状态 StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; if (isQuit(msg)) { // 切换到退出状态 transitionTo(mQuittingState); } else { // 处理消息 while (!curStateInfo.state.processMessage(msg)) { curStateInfo = curStateInfo.parentStateInfo; if (curStateInfo == null) { mSm.unhandledMessage(msg); break; } } } return (curStateInfo != null) ? curStateInfo.state : null; }
在
while
循环中,先执行当前状态的processMessage(msg)
函数,如果返回false
,意味着当前状态处理不了。那么查找它的父状态,存在父状态则交给父状态处理。如果知道根状态,改消息还未被处理,那么就执行StateMachine:unhandledMessage(msg)
函数,交给状态机去处理。消息处理是一个从上到下,从子到父的过程,直到被消化为止。 -
transitionTo(State)
状态切换private final void transitionTo(IState destState) { if (mTransitionInProgress) { transition already in progress to " + mDestState + ", new target state=" + destState); } mDestState = (State) destState; }
从代码上可以看出,状态切换的时候并不是立即切换状态,只是将新的状态赋值给
mDestState
变量,那么状态是在哪切换?mDestState
变量会在哪被使用?
其实状态是函数performTransitions()
中切换的,mDestState
变量也是在这里面被使用到。在
handleMessage(msg)
中,执行完processMsg(msg)
之后,会去执行performTransitions()
函数,目的就是为了检查当前状态是否改变。// 只保留了核心代码 private void performTransitions(State msgProcessedState, Message msg) { State destState = mDestState; // 切换到新状态 if (destState != null) { while (true) { // 1.根据新状态构建新的状态栈,存储在mTempStateStack,并返回跟新状态相同的父节点 StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); mTransitionInProgress = true; // 2.跟距相同的父节点去执行旧状态的exit()函数 invokeExitMethods(commonStateInfo); // 3.反转mTempStateStack,并存储到mStateStack中 int stateStackEnteringIndex = moveTempStateStackToStateStack(); // 4.执行新状态的enter()函数 invokeEnterMethods(stateStackEnteringIndex); // 5.将deferMessage(msg)的消息插入消息队列 moveDeferredMessageAtFrontOfQueue(); if (destState != mDestState) { destState = mDestState; } else { break; } } mDestState = null; } }
这个函数做了5件事情,一件件去品:
-
setupTempStateStackWithStatesToEnter()
构建新状态栈,存储在mTempStateStackprivate final StateInfo setupTempStateStackWithStatesToEnter(State destState) { mTempStateStackCount = 0; StateInfo curStateInfo = mStateInfo.get(destState); do { mTempStateStack[mTempStateStackCount++] = curStateInfo; curStateInfo = curStateInfo.parentStateInfo; } while ((curStateInfo != null) && !curStateInfo.active); return curStateInfo; }
入栈顺序是:“父在上,子在底“,当前状态在栈底,父状态在上面。
do...while()
中!curStateInfo.active
这个判断很关键,在遍历父状态的过程中,如果这个父状态已经被激活了,表示该父状态也是之前旧状态的父状态,那么循环结束,并返回该相同的父状态。每个状态只要一个父状态,这样就避免了切换的复杂性,使状态树更清晰,更灵活。 -
invokeExitMethods(commonStateInfo)
private final void invokeExitMethods(StateInfo commonStateInfo) { while ((mStateStackTopIndex >= 0) && (mStateStack[mStateStackTopIndex] != commonStateInfo)) { State curState = mStateStack[mStateStackTopIndex].state; curState.exit(); mStateStack[mStateStackTopIndex].active = false; mStateStackTopIndex -= 1; } }
从旧状态 --> 与新状态相同的父节点,依次执行
exit()
函数 moveTempStateStackToStateStack()
与invokeEnterMethods()
在start()
的流程中分析过,这里不再累赘,重新填充mStateStack
, 并依次执行enter()
函数,父在底,子在上。-
最后看一下
moveDeferredMessageAtFrontOfQueue()
,例子中调用的deferMessage(msg)
函数,最后起作用的就是在这个函数中。private final void deferMessage(Message msg) { Message newMsg = obtainMessage(); newMsg.copyFrom(msg); mDeferredMessages.add(newMsg); } private final void moveDeferredMessageAtFrontOfQueue() { for (int i = mDeferredMessages.size() - 1; i >= 0; i--) { Message curMsg = mDeferredMessages.get(i); sendMessageAtFrontOfQueue(curMsg); } mDeferredMessages.clear(); }
执行
deferMessage(msg)
的时候,只是将msg
插入到mDeferredMessages
这个列表中,执行moveDeferredMessageAtFrontOfQueue()
的时候遍历mDeferredMessages
列表,并将里头的元素一一插入到消息队列的最前头,然后清空mDeferredMessages
列表。这也就是为什么在deferMessage(msg1)
之后sendMessage(msg2)
,msg1总是先执行。
-
-
总结
整个
StateMachine
就分析到这了,主要的就是三个部分:- 添加状态
- 状态机启动
- 状态切换
接下来会分析Android蓝牙的相关源码。
参考1
参考2
参考3