前文回顾
这一篇会进一步介绍一些高级应用场景;
State的层级关系
State切换时的调用时序
根据之前的了解,我们知道在State 切入时会调用其enter() 方法,在退出时会调用其exit() 方法; 但是那都是基于评级关系而言的,当State 存在层级关系的时候,enter() /exit() 的调用时序是否会存在差异呢?
为了探究这点,我们先假设有如下State层级关系的状态机:
 然后,我们设置State1 为初始状态,并沿着如下顺序进行状态切换:
State1 -> State2 -> State3 -> State1
其调用时序如下:  虚线箭头表示切换的方向,虚线旁的调用栈即为调用时序;
并列关系的状态切换规律已经理清了,那么我们接下来看看层级关系内的状态切换:
从结果来看,规律似乎是:“状态A到状态B切换时的调用栈就是从状态A回到空节点,再切到状态B的最短路线” (这里我划掉了,可见是错误的,要看结论的请直接移步文档末尾;)
State1 ->State2 ->State3 /State4 的现象均如此,但如果在此时,我们从State4 切换到State3 ,就会发现不满足:
(下图省略了已经讨论完毕的State1 与State2 ,并再添加了一个State5 ,作为State4 的子状态)
 可以看到,具有层级关系的状态之间切换会有如下两个特征:
- 从父状态进入子状态,直接依次向下调用子状态的
enter() ,直到目标状态为止; - 从子状态进入父状态,会从自身开始,依次向上调用
exit() ,直到调用目标状态的exit() 后再调入目标状态的enter() ;
可能这里会让人很疑惑:为什么进入父状态时需要先退出再进入;
为了更好理解,这里再引入一个State6 ,并指定其为State3 的子状态,即与State4 并列:  其各个状态切换的调用栈如下图:
除去State4 与State5 的状态切换情况(上面已经讨论过了),其余状态切换的调用栈如下图:

可见,State3 下的两个子状态:State4 与State6 ,两者切换时并不会调用State3 的exit() ;因此可以确定,调用栈并不是单纯的路线顺序;
根据分析代码,我注意到一段关键代码:
private void performTransitions(State msgProcessedState, Message msg) {
...
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
mTransitionInProgress = true;
invokeExitMethods(commonStateInfo);
...
}
这里告诉了我状态切换的一个向上查找的标准:退到非两个状态本身的公共父状态为止
两个重点:
- 切换前后两个状态本身不能作为这里的公共父状态;
- 找到的这个公共父状态不会退出;
举例:
State5 -> State4
State4 为State5 的父状态,但是由于上面提到,切换前后两个状态本身不能作为公共父状态,因此还需要向上找,即State3 为State5 与State4 的公共父状态;- 因此,调用栈为:
State5.exit() -> State4.exit() -> (State3 ,但不会走任何回调) -> State4.enter() ,与上面结论一致; State5 -> State6
State5 与State6 没有直接联系,因此向上寻找,可以发现最早将二者联系起来的是State3,即为公共父状态;- 因此,调用栈为:
State5.exit() -> State4.exit() -> (State3 ,但不会走任何回调) -> State6.enter() ,与上面结论一致; State4 -> State6
- 显而易见,
State4 与State6 的公共父状态为State3 ; - 因此,调用栈为:
State4.exit() -> (State3 ,但不会走任何回调) -> State6.enter() ,与上面结论一致;
结论: 一个比较容易的记忆方法就是:
"状态A到状态B切换时的调用栈就是从状态A回到两者的公共父状态,再走到状态B的最短路线"
State切换时内部实现
上面已经介绍了现象即规律,不想深究的可以就此打住; 但是为了将状态机吃透,便于后续开发时可以有更多参考的模型,因此这里会着重介绍下这个机制实现所依赖的数据结构:
private StateInfo mStateStack[]; private int mStateStackTopIndex = -1; private StateInfo mTempStateStack[]; private int mTempStateStackCount;
前面已经介绍过,状态切换本质是performTransitions() 方法实现的,其核心实现截取如下:
State destState = mDestState;
if (destState != null) {
while (true) {
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
mTransitionInProgress = true;
invokeExitMethods(commonStateInfo);
int stateStackEnteringIndex = moveTempStateStackToStateStack();
invokeEnterMethods(stateStackEnteringIndex);
moveDeferredMessageAtFrontOfQueue();
if (destState != mDestState) {
destState = mDestState;
} else {
break;
}
}
mDestState = null;
}
这里稍微展开一点,依次看看setupTempStateStackWithStatesToEnter 和moveTempStateStackToStateStack 的实现:
private 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;
}
private final int moveTempStateStackToStateStack() {
int startingIndex = mStateStackTopIndex + 1;
int i = mTempStateStackCount - 1;
int j = startingIndex;
while (i >= 0) {
mStateStack[j] = mTempStateStack[i];
j += 1;
i -= 1;
}
mStateStackTopIndex = j - 1;
return startingIndex;
}
小结: 虽然在上方 《State切换时的调用时序》 章节中,我的结论是:
"状态A到状态B切换时的调用栈就是从状态A回到两者的公共父状态,再走到状态B的最短路线"
但是从这里的代码来看,实际上逻辑并非现象看上去那么线性,具体说来是经过了这几个步骤:
- 找到当前状态与目标状态的公共父状态;--
setupTempStateStackWithStatesToEnter() - 将公共父状态到目标状态的层级关系录入mTempStateStack;--
setupTempStateStackWithStatesToEnter() - 从当前状态开始,依次执行exit()方法,直到退到公共父状态(公共父状态不会调用exit(),即不会退出);--
invokeExitMethods() - 从公共父状态在mStateStack的下标为止开始,将mTempStateStack中的状态栈反序追加到mStateStack中;--
moveTempStateStackToStateStack() - mStateStack追加完成后,再以公共父状态在mStateStack的下标为止开始至mStateStack有效数据末尾,依次执行State的enter()方法;--
invokeEnterMethods()
后记
暂时就写这么多吧,实际上StateMachine 还有很多细节可以挖掘,包括日志的记录,并发的处理,以及processMessage() 方法返回值决定是否调用父状态的processMessage() 等。但这些都不会影响我们对StateMachine 工作机制的理解,因此这里就暂不展开了。
感兴趣的可以自行了解。
|