文档开发者中心
我的

【综合】Android StateMachine详解

作者:HW-LC


Android StateMachine是有限状态机的一个比较经典的实现,现在项目中业务的主状态机和策略状态机都用到了这个工具,而且后面跨平台的时候也可能会用到,所以抽时间看了下具体的实现(以便后面如果有业务需要,可以亲手撸一个C/C++的版本)。

我用sample code的方法来讲解StateMachine的具体实现:


public class MyStateMachine extends StateMachine {

private static final String TAG = 'MyStateMachine';

…

private State mDefaultState = new DefaultState();

private State mDisabledState = new DisabledState();

private State mEnabledState = new EnabledState();

private State mDisabledAState = new DisabledAState();

private State mDisabledBState = new DisabledBState();

public MyStateMachine(String name) {

super(name);

addState(mDefaultState);

addState(mDisabledState, mDefaultState);

addState(mDisabledAState, mDisabledState);

addState(mDisabledBState, mDisabledState);

addState(mEnabledState, mDefaultState);

setInitialState(mDisabledAState);

setDbg(true);

start();

}

...

}

这段代码很简单,主要如下:

(1) 实例化很多状态

(2) 通过addState插入到状态机

(3) 设置状态机初始化状态

(4) 通过start()启动状态机

我们先看下addState是如何插入到状态机的:

private final StateInfo addState(State state, State parent) {

…

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);

}

…

stateInfo.state = state;

stateInfo.parentStateInfo = parentStateInfo;

stateInfo.active = false;

if (mDbg) mSm.log('addStateInternal: X stateInfo: ' + stateInfo);

return stateInfo;

}

这段代码的意思是:如果当前状态有父状态,且父状态不在mStateInfo这个HashMap中,则把父状态添加到mStateInfo中,另外构造当前状态的stateInfo,然后设置父状态为指定状态,设置默认active信息为false(也就是未激活),也添加到mStateInfo中,也就是说所有addState添加进来的状态都会存放在mStateInfo这个HashMap中。

接下来看下状态机如何启动的:

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();

}

private final void completeConstruction() {

...

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);</span></span></span></span></p><p><span style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); box-sizing: border-box; color: rgb(51, 51, 51); font-family: 'Microsoft YaHei', '\\5FAE软雅黑', Arial, sans-serif; font-size: 12pt; text-align: justify; background-color: rgb(255, 255, 255);">

mStateStack = new StateInfo[maxDepth];

mTempStateStack = new StateInfo[maxDepth];

setupInitialStateStack();

...

sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));

}

这段代码的意思是:start()状态机主要是调用StateMachine中的completeConstruction,completeConstruction会遍历每个状态节点,找到所有节点的最大深度,用这个深度来定义两个栈:mStateStack和mTempStateStack(整个StateMachine的核心就是这两个栈),然后调用setupInitialStateStack初始化这两个栈,最后发送SM_INIT_CMD消息来真正的启动整个StateMachine。

我们看下StateMachine的核心栈是如何初始化的:

private final void setupInitialStateStack() {

StateInfo curStateInfo = mStateInfo.get(mInitialState);

for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {

mTempStateStack[mTempStateStackCount] = curStateInfo;

curStateInfo = curStateInfo.parentStateInfo;

}

mStateStackTopIndex = -1;

moveTempStateStackToStateStack();

}

这段代码的意思是:拿到业务侧指定的初始化状态mInitialState(通过setInitialState设置),然后把初始化状态放到mTempStateStack,然后继续找到其父状态,如果有的话把父状态也加到mTempStateStack,最终把初始状态的最顶层父状态加到mTempStateStack为止(按sample code来看,目前mTempStateStack从栈底到栈顶的元素是:mDisabledAState,mDisabledState,mDefaultState)。最后把mTempStateStack的数据转移到mStateStack。

我们接下来看看mTempStateStack的数据是怎么转移到mStateStack的:

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);</span></span></span></span></p><p><span style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); box-sizing: border-box; color: rgb(51, 51, 51); font-family: 'Microsoft YaHei', '\\5FAE软雅黑', Arial, sans-serif; font-size: 12pt; text-align: justify; background-color: rgb(255, 255, 255);">

mStateStack[j] = mTempStateStack[i];

j += 1;

i -= 1;

}

mStateStackTopIndex = j - 1;

return startingIndex;

}

这段代码的意思是:startingIndex初始化为0(mStateStack目前是空栈),i是mTempStateStack的栈偏移,当前是2,j是mStateStack的栈偏移,当前是0。然后把mTempStateStack的元素依次压入mStateStack,并且更新各自栈偏移(结果是:mTempStateStack为空栈,mStateStack从栈底到栈顶依次是:mDefaultState,mDisabledState,mDisabledAState)。

前面的代码都是构建状态机和初始化状态机Stack,再看看SM_INIT_CMD是如何启动StateMachine的:

public final void handleMessage(Message msg) {

...

mMsg = msg;

/** State that processed the message */

State msgProcessedState = null;

if (mIsConstructionCompleted) {

/** Normal path */

msgProcessedState = processMsg(msg);

} else if ((mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {

/** Initial one time path. */

if (mDbg) mSm.log('processMsg');

mIsConstructionCompleted = true;

invokeEnterMethods(0);

} else {

throw new RuntimeException('StateMachine.handleMessage: '

+ 'The start method not called, received msg: ' + msg);

}

performTransitions(msgProcessedState, msg);

...

}

这个函数是整个StateMachine的核心,SM_INIT_CMD走的是第二条分支,也就是调用invokeEnterMethods(0)。

我们看下invokeEnterMethods是做了哪些事情:

private final void invokeEnterMethods(int stateStackEnteringIndex) {

for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {

if (mDbg) mSm.log('invokeEnterMethods: ' + mStateStack[i].state.getName());

mStateStack[i].state.enter();

mStateStack[i].active = true;

}

}

这个代码就很简单了,SM_INIT_CMD调用invokeEnterMethods的初始栈偏移是0,也就是从mStateStack的栈底开始遍历,挨个调用state的enter方法(sample code中也就是挨个调用mDefaultState,mDisabledState和mDisabledAState的enter方法),然后设置state的active属性为true,到此为止整个StateMachine的初始化就做完了。

这个初始化行为跟StateMachine官方说明是一致的:

After construction the programmer calls <code>start</code> which initializes and starts the state machine. The first action the StateMachine is to the invoke <code>enter</code> for all of the initial state's hierarchy, starting at its eldest parent.

从initial state的最顶层父状态(mDefaultState)逐层调用enter方法。

状态机初始化对应的栈图如下:


接下来看下StateMachine如何去处理Message:

public final void sendMessage(int what) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what));

}

我们可以看到StateMachine的message都是交给SmHandler的handleMessage去处理的(handleMessage的代码在讲SM_INIT_CMD的时候已经贴过了),正常情况下会先走第一条分支,使用processMsg来处理,具体如下:

private final State processMsg(Message msg) {

StateInfo curStateInfo = mStateStack[mStateStackTopIndex];

...

while (!curStateInfo.state.processMessage(msg)) {

curStateInfo = curStateInfo.parentStateInfo;

if (curStateInfo == null) {

mSm.unhandledMessage(msg);

break;

}

}

...

return (curStateInfo != null) ? curStateInfo.state : null;

}

这段代码的意思是:调用当前状态的processMessage方法(每个state都会自己去override这个方法,默认直接返回NOT_HANDLED),如果当前状态无法处理则返回NOT_HANDLED,StateMachine会向上找到其父状态,然后调用父状态的processMessage直到返回HANDLED,如果其最顶层父状态也无法处理,则交给StateMachine的unhandledMessage处理,最后返回处理消息的当前状态。

这个处理消息的行为跟StateMachine的官方说明是一致的:

If a child state is unable to handle a message it may have the message processed by its parent by returning false or NOT_HANDLED. If a message is not handled by a child state or any of its ancestors, <code>unhandledMessage</code> will be invoked to give one last chance for the state machine to process the message.

如果当前状态无法处理(返回false或者NOT_HANDLED),消息则交给其父状态处理,如果无法被任何父状态(祖先)处理,则会交给StateMachine的unhandleMessage处理。

最后看下StateMachine中既核心,又复杂的一个函数performTransitions(这里假设sample code中从mDisabledAState跳转到mEnabledState):

private void performTransitions(State msgProcessedState, Message msg) {

State orgState = mStateStack[mStateStackTopIndex].state;

State destState = mDestState;

if (destState != null) {

while (true) {

StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);

invokeExitMethods(commonStateInfo);

int stateStackEnteringIndex = moveTempStateStackToStateStack();

invokeEnterMethods(stateStackEnteringIndex);

moveDeferredMessageAtFrontOfQueue();

if (destState != mDestState) {

destState = mDestState;

} else {

break;

}

}

mDestState = null;

}

}

这段代码的意思是:先找到当前状态(mStateStack栈顶就是当前状态),拿到mDestState的值(默认是null,则这个函数啥都不做,业务侧可以通过transitionTo来设置这个值,从而控制StateMachine的状态跳转)。

假设业务侧在handleMessage中通过transitionTo已经设置过mDestState了(mEnabledState),则会进到while true循环中,我们先看下setupTempStateStackWithStatesToEnter:

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;

}

这段代码的意思是:把destState压入到mTempStateStack,然后找父状态,如果父状态存在,且active为false则继续压栈,直到找到一个active为true的父状态(这个地方实现比较隐蔽,当前状态的所有父状态的active都是true,所以这里其实是要找到当前状态和destState的最近公共父状态,sample code中则是mDefaultState),找到这个最近公共父状态,就能确定state的exit路线和enter路线。

setupTempStateStackWithStatesToEnter做完之后,destState到最近公共父状态的逐层信息就压入到mTempStateStack了(sample code中,mTempStateStack中只有mEnabledState,最近公共父状态是mDefaultState)。

再看看invokeExitMethods:

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;

}

}

这段代码的意思是:如果mStateStack的栈偏移大于等于0,且当前状态不等于最近公共父状态,则调用当前状态的exit,并且置当前状态的active为false,把当前状态出栈(此时mStateStack栈顶存放的是当前状态的父状态),直到mStateStack的栈顶是最近公共父状态(到此为止state的exit路线执行完毕,所有state的exit方法都已经调用,sample code中则是逐步调用mDisabledAState,mDisabledState的exit方法,更新mStateStack的栈偏移为1,也就是说mStateStack中目前只有mDefaultState一个元素了)。

接下来继续调用moveTempStateStackToStateStack(这个代码前面贴过,这里就不细讲),就是把mTempStateStack的元素挨个压入到mStateStack,并且更新各自的栈偏移,到此为止mTempStateStack已经空栈了,mStateStack中有两个元素(栈底到栈顶分别是mDefaultState和mEnabledState)。

接下来调用invokeEnterMethods,参数是moveTempStateStackToStateStack之前mStateStack的栈偏移(值为1,当时mStateStack只有mDefaultState一个元素):

private final void invokeEnterMethods(int stateStackEnteringIndex) {

for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {

if (mDbg) mSm.log('invokeEnterMethods: ' + mStateStack[i].state.getName());

mStateStack[i].state.enter();

mStateStack[i].active = true;

}

}

从栈偏移1的地方向栈顶遍历,逐个调用state的enter方法,并且设置state的active为true。

这个状态跳转的行为跟StateMachine的官方说明是一致的:

Since the states are arranged in a hierarchy transitioning to a new state causes current states to be exited and new states to be entered. To determine the list of states to be entered/exited the common parent closest to the current state is found. We then exit from the current state and its parent's up to but not including the common parent state and then enter all of the new states below the common parent down to the destination state. If there is no common parent all states are exited and then the new states are entered.

文章中说的最近公共父状态就是common parent closest to the current state,mStateStack逐个出栈并调用状态的exit,直到最近公共父状态。然后记录当前栈偏移为stateStackEnteringIndex,把mTempStateStack中的数据转移到mStateStack,然后从stateStackEnteringIndex到栈顶逐个调用enter。

状态跳转对应的栈图如下:


最后用moveDeferredMessageAtFrontOfQueue把之前defer的message放到队列头部,等待handler的handleMessage处理:

private final void moveDeferredMessageAtFrontOfQueue() {

for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {

Message curMsg = mDeferredMessages.get(i);

sendMessageAtFrontOfQueue(curMsg);

}

mDeferredMessages.clear();

}

到此为止,整个状态机就讲完了。


点赞
收藏
回复
10
分享
举报
浏览3448 发布于2019-11-11 01:48未知归属地
全部评论
最多点赞
最新发布
最早发布

哎呦,楼主不错哦

10楼回复于2019-11-11 08:00 未知归属地

分享给我的伙计去了

9楼回复于2019-11-11 07:34 未知归属地

好文好文

8楼回复于2019-11-11 07:34 未知归属地

还有这篇:RabbitMQ环境安装和使用

7楼回复于2019-11-11 03:35 未知归属地

看了下这篇也是这位楼主的:人脸识别算法集成

6楼回复于2019-11-11 03:35 未知归属地

文章好的很

5楼回复于2019-11-11 03:32 未知归属地

跟楼主点个大赞

4楼回复于2019-11-11 03:32 未知归属地

很八错,转走了

3楼回复于2019-11-11 03:00 未知归属地

把这个好帖转走喽

2楼回复于2019-11-11 02:49 未知归属地

学习了,栈图很详细,点赞

1楼回复于2019-11-11 02:49 未知归属地
写回答
新增插入模板功能
一键使用模板,快速填写内容,轻松发帖~
知道了
  • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
  • 如您发布的内容为转载内容,请注明内容来源。

我要发帖子

了解社区公约,与您携手共创和谐专业的开发者社区。