[Android] 状态机 StateMachine 源码剖析

4,594 阅读13分钟

1. 案例

案例:我们常见的汽车,我们可以使用它行驶,也可以将它停止在路边。当它在行驶的过程中,需要不断的检测油量,一旦油量不足的时候,就将陷入停止状态。而停止在路边的汽车,需要点火启动,此时将检测车中的油量,当油量不足的时候,汽车就需要去加油站加油。

当我们对汽车的状态和行为进行抽象,汽车的状态可以有 :

  • 停车 STOP
  • 行驶 RUN
  • 检测油量 CHECK_OIL
  • 加油 ADDING_OIL

而我们可以对汽车的操作可以是:

  • 停车 ACTION_STOP
  • 行驶 ACTION_RUN
  • 加油 ACTION_ADD_OIL

我们建立一个二维表,将状态和可操作的行为组合在一起:

状态行为对应表

2. HSM

我们通过这个状态表构建我们的状态引用关系模型:

状态转化模型

这幅状态图实际上一个相对复杂的网状图形,当构建一个更为复杂的系统的时候,这种网状图将会以成倍的复杂性递增。为了解决这个问题,我们需要将这种网状的状态机转化为一个树状的层次状态机,也叫 HSM (Hierarchical State Machine)。我们可以将上述的状态模型转化为:

层次状态图

这张图里,将 STOP 作为根节点,从层次上作为其他状态节点的父节点。

  • STOP 作为初始状态
  • 发生了 ACTION_ADD_OIL 动作,STOP 状态就变成了 ADDING_OIL 状态
  • ADDING_OIL 结束,发生了 ACTION_RUN 动作,就需要弹出 ADDING_OIL状态 ,传入到 CHECK_OIL,然后传入 RUN 状态。

3. [StateMachine] 初始化

StateMachineAndroid 系统提供的 HSM 状态机的实现,它的源码在包com.android.internal.util下。StateMachine 提供了三个构造方法,但这三个方法大同小异:

protected StateMachine(String name) {
        mSmThread = new HandlerThread(name);
        mSmThread.start();
        Looper looper = mSmThread.getLooper();

        initStateMachine(name, looper);
}

构造器调用 initStateMachine 函数,这个函数需要传入了一个 Looper 对象,StateMachine 对象所有的操作都需要在这个 Looper 所在的线程中运行。而之间的通讯是通过 SmHandler 对象传递。

private void initStateMachine(String name, Looper looper) {
        mName = name;
        mSmHandler = new SmHandler(looper, this);
    }

上面我们说了,StateMachineHSM 状态机,构造它的时候,需要指定它的层次关系,这需要调用 addState 函数,这个函数有两个参数,第第二个参数代表的是第一个参数的父节点:

protected final void addState(State state, State parent) {
        mSmHandler.addState(state, parent);
}

而根节点,又称为初始状态节点,需要通过 setInitialState 函数指定:

 protected final void setInitialState(State initialState) {
        mSmHandler.setInitialState(initialState);
}

这里,不论设置什么样的节点,都需要通过 mSmHandler 对象设置,比如,当通过调用 StateMachine.addState 添加节点的时候,需要调用到 SmHandler.addState 函数:

//code SmHandler
private final StateInfo addState(State state, State parent) {
            if (mDbg) {
            //debug开关可以通过 StateMachine.setDbg接口设置打开
                mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
                        + ((parent == null) ? "" : parent.getName()));
            }
            StateInfo parentStateInfo = null;
            // StateInfo 表示在 HSM 树中的状态节点
            if (parent != null) {
                parentStateInfo = mStateInfo.get(parent);
                //mStateInfo 是一个hashmap对象
                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.
            if ((stateInfo.parentStateInfo != null)
                    && (stateInfo.parentStateInfo != parentStateInfo)) {
                    //不允许一个节点存在两个父节点
                throw new RuntimeException("state already added");
            }
            stateInfo.state = state;
            stateInfo.parentStateInfo = parentStateInfo;
            //构建父子的层次关系
            stateInfo.active = false;
            if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
            return stateInfo;
        }

mStateInfo是一个 HashMap<State, StateInfo> 类型的对象, 而 StateInfo 类是用于记录状态 State 对象信息,和父节点信息的 HSM 节点对象

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

按照我们刚才对 Car 这个模型的抽象,我们可以定义出一个 Car 的状态机:


public class Car extends StateMachine {
    ....
    public Car(String name) {
        super(name);
        this.addState(mStopState,null);
        //mStopState 作为根节点状态,没有父节点
            this.addState(mAddOilState,mStopState);
            //mAddOilState 作为mStopState 的子状态
            this.addState(mCheckOilState,mStopState);
            //mCheckOilState 作为mStopState 的子状态
                this.addState(mRunState,mCheckOilState);
                //mRunState 作为mCheckOilState 的子状态
        this.setInitialState(mStopState);
        // mStopState 为初始状态
    }
}

当我们构造完我们的树形结构了以后,我们就可以将我们的状态机启动起来,这个启动依赖于 StateMachine.start 函数:

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
}

StateMachine.start 中调用 SmHandler.completeConstruction用于提交我们之前的所有操作:

 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.
             */
            int maxDepth = 0;// step1
            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();//以初始状态为栈底保存到 mStateStack

            /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
            sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
            if (mDbg) mSm.log("completeConstruction: X");
        }

按照我们的 HSM 模型,以 STOP 状态为基础状态的时候,那么我们以这个状态为栈底向上延伸,我们可以得到两个栈,分别是:

stack1: [STOP,CHECK_OIL,RUN]

stack2: [STOP,ADD_OIL]

stack1 的最大深度为 3 , stack2 的最大深度为 2 。那么 stack1 就可以应用于 stack2 的情况。 completeConstruction 代码中 step1 段的代码就是这个目的,找到一个最大的栈,用于给所有的栈情况使用。

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;
            }
            //将初始状态位根状态以0->N的顺序存入 tempStack

            // Empty the StateStack
            mStateStackTopIndex = -1;
            moveTempStateStackToStateStack();//将 tempstack 倒叙复制给 stateStack
        }

mTempStateStack 是一个中间变量,它存的是倒叙的 mStateStack 。比如我们的初始状态是 RUN 。那么我们需要不断循环将 RUN 的父节点存入 mTempStateStack 得到:

mTempStateStack :[RUN,CHECK_OIL,STOP]

这时候我们需要调用 moveTempStateStackToStateStack 函数将它倒叙复制到 mStateStack 对象中,保证当前状态 RUN 位于栈顶:

mStateStack: [STOP,CHECK_OIL,RUN]

mStateStackTopIndex 变量指向 mStateStack 的栈顶。刚才的这个例子,mStateStackTopIndex 的值为 2 ,指向 RUN 所在的数组索引位置。

到了 start 函数调用的这一步,我们就完成了一个树形数据结构和初始状态的设置,接下来,我们就可以往我们的状态机上发送我们的指令。

4. [StateMachine] 处理消息

我们通过上面的手段构造完一个状态机以后,就可以通过指令让这个状态机去处理消息了。我们先给我们的状态机开一些外部调用的接口:

public interface ICar {
    
    public void run();
    public void stop();
    public void addOil();
    
}

public class Car extends StateMachine implements ICar{
    ....
}

public void func() {
    ICar car = new Car("Ford");
    car.addOil();
    car.run();
    car.stop();
}

当我们要向我们的状态机发送指令的时候,需要调用状态机的 sendMessage(...) 函数,这套函数跟 android.os.Handler 提供的 api 的含义一模一样。实际上,状态机在处理这种消息的时候,也是采用 Handler 的方式,而我们上面反复提到的 SmHandler 对象实际上就是 Handler 对象的子类。

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));//通过Handler方式发送消息
}

这样,我们就可以通过这个函数去实现我们的几个接口方法:

public class Car extends StateMachine implements ICar{ 
    ...
    public void run() {
        this.sendMessage(ACTION_RUN);
    } 
    
    public void stop() {
        this.sendMessage(ACTION_STOP);
    }
    
    public void addOil() {
        this.sendMessage(ACTION_ADD_OIL);
    }
}

根据我们对 Handler 类的了解,每当我们通过 Handler.sendMessage 函数发送一个消息的时候,都将在 Looper 的下个处理消息执行的时候,回调 Handler.handleMessage(Message msg) 方法。由于 SmHandler 继承于 Handler,并且它复写了 handleMessage 函数,因此 , 消息发送之后,最后将回调到SmHandler.handleMessage 方法中。

//code SmHandler
public final void handleMessage(Message msg) {
            if (!mHasQuit) {
                if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);

                /** Save the current message */
                mMsg = msg;

                /** State that processed the message */
                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;
                    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");
            }
        }

SmHandler.handleMessage 函数主要执行以下几个操作:

  1. 根据 mHasQuit 判断是否退出,如果退出将不执行后续指令
  2. 判断是否初始完成(根据变量mIsConstructionCompleted),如果初始化完成调用 processMsg 将消息抛给当前状态执行
  3. 如果尚未初始化,并且接受的是初始化命令 SM_INIT_CMD 将执行一次初始化操作
  4. 当命令执行结束后,执行 performTransitions 函数用于转变当前状态和 mStateStack

我们先接着上面第三个主题 [StateMachine] 初始化 看下第三步。 SM_INIT_CMD 指令的发出位于 SmHandler.completeConstruction 函数中:

//code SmHandler
 private final void completeConstruction() {
            ...
            /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
            sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
            ...
        }

处理初始化消息的时候会先将 mIsConstructionCompleted 设置为 true ,告诉状态机已经初始化过了,可以让状态处理消息了。然后调用了个 invokeEnterMethods 函数。这个函数的目的是回调当前 mStateStack 栈中所有的活动状态的 enter 方法。并且将非活跃状态设置为活跃态:

private final void invokeEnterMethods(int stateStackEnteringIndex) {
            for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
                mStateStack[i].state.enter();
                mStateStack[i].active = true;
            }
        }

这样,如果我们的初始状态是 STOP 的话,我们就可以在后台打印中看到:

//console output:
output: [StateMachine] StopState enter

如果我们的初始状态是 RUN 状态的话就可以看到:

//console output:
output: [StateMachine] StopState enter
output: [StateMachine] CheckOilState enter
output: [StateMachine] RunState enter

上面就是处理初始化消息的过程,到这一步,初始化的过程算是完整走完。我们继续来看初始化后的逻辑,当初始化已经结束之后,再收到的消息将通过 processMsg 函数提交给合适的状态执行。

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);
                        //当没有状态可以处理当前消息的时候回调unhandledMessage 
                        break;
                    }
                }
            }
            return (curStateInfo != null) ? curStateInfo.state : null;
        }

processMsg 会先判断当前是否是退出消息,如果 isQuit 成立,将转入 mQuittingState 状态。我们将在后面分析如何执行退出操作,这块东西,我们暂且有个印象。当并非退出消息时候,将会分配给当前状态处理,如果当前状态处理不了,将委托给父状态处理。比如当前我们的初始状态是 RUN 。那么对应的 mStateStack 为:

[STOP,CHECK_OIL,RUN]

我们给状态的测试代码是:

private class BaseState extends State {
        @Override
        public void enter() {
            log(" enter "+this.getClass().getSimpleName());
            super.enter();

        }

        @Override
        public void exit() {
            log(" exit "+this.getClass().getSimpleName());
            super.exit();
        }
}

public class StopState extends BaseState {

        @Override
        public boolean processMessage(Message msg) {
            log("StopState.processMessage");
            return HANDLED;//处理消息
        }
    }

public class CheckOilState extends BaseState {
        @Override
        public boolean processMessage(Message msg) {
            log("CheckOilState.processMessage");
            return NOT_HANDLED;// 不处理消息
        }
}

public class RunState extends BaseState {}

我们往状态机 Car 发送一条消息:

Car car = new Car();
car.sendMessage(0x01);

我们将在后台打印出log:

 --> enter StopState
 --> enter CheckOilState
 --> enter RunState
 // 初始化结束
 -->[StateMachine]:handleMessage 1
-->CheckOilState.processMessage // run状态不处理,扔给checkoil状态
-->StopState.processMessage // checkoil状态不处理,扔给stop 状态

当然,如果你并不希望消息被委托调用,你可以在初始状态调用 processMessage 函数的时候,返回 HANDLED 常量,这样就不会往下调用。

5. [StateMachine] 状态转换

通常,我们会在 State.processMessage 内部,通过调用 transitionTo 函数执行一次状态转换,而调用这个函数只是将你要转换的状态存入一个临时的对象中:

protected final void transitionTo(IState destState) {
        mSmHandler.transitionTo(destState);
}
private final void transitionTo(IState destState) {
            mDestState = (State) destState;
}

真正的状态转换将发生在 SmHandler.handleMessage 函数执行之后:

 public final void handleMessage(Message msg) {
            if (!mHasQuit) {
                ...
                performTransitions(msgProcessedState, msg);//变更状态
            }
        }

这里将调用 performTransitions 函数完成状态转换,假如,现在的状态是 RUN 状态,当需要转成 ADD_OIL 状态的时候,将进行一下转变:

/**
初始:
mStateStack : [ STOP,CHECK_OIL,RUN]
*/

private void performTransitions(State msgProcessedState, Message msg) {
            State orgState = mStateStack[mStateStackTopIndex].state;
            //orgState记录当前状态
            State destState = mDestState;
            //destState 记录要转变的目标状态
            if (destState != null) {
                while (true) {
                    StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
                    //查找跟目标状态的公共节点状态,此时为 STOP 状态节点
                    invokeExitMethods(commonStateInfo);
                    //从栈顶一直到commonStateInfo(不包含) 所在的位置执行退出操作
                    int stateStackEnteringIndex = moveTempStateStackToStateStack();
                    invokeEnterMethods(stateStackEnteringIndex);
                    moveDeferredMessageAtFrontOfQueue();
                    //将Deferred 消息放入队列头部优先执行
                    if (destState != mDestState) {
                        destState = mDestState;
                    } else {
                        break;
                    }
                }
                mDestState = null;
            }
            if (destState != null) {
                if (destState == mQuittingState) {
                    //TODO clean
                } else if (destState == mHaltingState) {
                    //TODO halt
                }
            }
        }

这段代码执行的时候,会先去寻找目标节点和当前节点的公共祖先节点,这是通过调用 setupTempStateStackWithStatesToEnter 调用的。StateMachine 的函数名起的见名知意,*Temp* 代表这个函数中要使用中间变量 mTempStateStack*ToEnter 代表需要对添加进的状态执行 State.enter 操作。

private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
            mTempStateStackCount = 0;//重置 mTempStateStack
            StateInfo curStateInfo = mStateInfo.get(destState);
            do {
                mTempStateStack[mTempStateStackCount++] = curStateInfo;
                curStateInfo = curStateInfo.parentStateInfo;
            } while ((curStateInfo != null) && !curStateInfo.active);
            //找到第一个 active 的状态节点。
            return curStateInfo;
        }

setupTempStateStackWithStatesToEnter 函数就是将目标节点的堆栈复制到 mTempStateStack 变量中,然后将最终相交的节点返回。这里采用 do-while的写法,说明这个函数的执行,至少包含一个 destState 元素。刚才从 RUN->ADD_OIL 的例子中,setupTempStateStackWithStatesToEnter 将返回 STOP 状态,mTempStateStack 的为:

mTempStateStack: {ADD_OIL}

我们回到 performTransitions 的流程,执行 setupTempStateStackWithStatesToEnter 完,将执行 invokeExitMethods 函数。

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

这个函数相当于将 mStateStack 栈中的非 commonStateInfo 进行出栈。

mStateStack: {STOP,CHECK_OIL,RUN} ->
invokeExitMethods(STOP) ->
mStateStack: {STOP}

执行完出栈后,只需要将我们刚才构建的 mTempStateStack 拷贝到 mStateStack 就可以构建新的状态栈了,而这个操作是通过 moveTempStateStackToStateStack 函数完成,而 moveTempStateStackToStateStack 我们刚才说过,实际上就是将 mTempStateStack 逆序赋值到 mStateStack。这样,我们就构建了一个新的 mStateStack:

mStateStack: {STOP,ADD_OIL}

这个时候,我们构建了一个新的状态栈,相当于已经切换了状态。performTransitions 在执行完 moveTempStateStackToStateStack 之后,调用 invokeEnterMethods 函数,执行非 active 状态的 enter 方法。之后执行 moveDeferredMessageAtFrontOfQueue 将通过 deferMessage 函数缓存的消息队列放到 Handler 消息队列的头部:

...

                    int stateStackEnteringIndex = moveTempStateStackToStateStack();
                    invokeEnterMethods(stateStackEnteringIndex);
                    moveDeferredMessageAtFrontOfQueue();
                    //将Deferred 消息放入队列头部优先执行
                    if (destState != mDestState) {
                        destState = mDestState;
                    } else {
                        break;
                    }
                    
                    ...

当我们完成状态的转换了以后,需要对两种特殊的状态进行处理,在 performTransitions 函数的末尾会判断两个特殊的状态:

1. HaltingState
2. QuittingState

6. [StateMachine] 状态机的退出

状态机的退出,StateMachine 提供了几个方法:

  1. quit: 执行完消息队列中所有的消息后执行退出和清理操作
  2. quitNow: 抛弃掉消息队列中的消息,直接执行退出和清理操作
  3. transitionToHaltingState: 抛弃掉消息队列中的消息,直接执行退出,不做清理

从上面的表述中看,quit 相对 halt 操作来说更加的安全。这个 Threadinterceptstop 方法很类似,很好理解。上面我们说到,退出状态 HaltingStateQuittingState 是在performTransitions 函数的末尾判断和执行的,我们来看下代码:

if (destState != null) {
                if (destState == mQuittingState) {
                    mSm.onQuitting();
                    cleanupAfterQuitting();//清理操作
                } else if (destState == mHaltingState) {
                    mSm.onHalting();//只是执行回调
                }
            }
            
            
private final void cleanupAfterQuitting() {
            if (mSm.mSmThread != null) {
                getLooper().quit();//退出线程
                mSm.mSmThread = null;
            }
            /*清空数据*/
            mSm.mSmHandler = null;
            mSm = null;
            mMsg = null;
            mLogRecords.cleanup();
            mStateStack = null;
            mTempStateStack = null;
            mStateInfo.clear();
            mInitialState = null;
            mDestState = null;
            mDeferredMessages.clear();
            mHasQuit = true;
        }

destState == mQuittingState 语句成立,将回调 StateMachine.onQuitting 函数,之后将执行 cleanupAfterQuitting 进行清理操作。清理操作中,会将线程清空,和其他数据变量清空,而如果 destState == mHaltingState 成立,StateMachine 将不执行任何的清理操作,通过回调 onHalting 函数来通知状态机退出。

7. 总结

Android 里面的这个 StateMachine 状态机在很多源码中都有涉及,代码也很简单,没有什么太大的难度,希望以上的总结能帮各位看官理解 StateMachine 源码的含义,并且能基于它,开发更多个性化的功能