状态机是一种面向对象的设计模式,用来描述对象在生命周期中的各种状态,以及它们之间的关系。状态机中每种状态只能进行某些特定的操作,且只能切换到某些状态。它的好处是使逻辑与状态机代码分离,提升代码可读性和可维护性。

一、架构

StateMachine 实现了 Android 状态机的基本逻辑,只允许 Android 系统内部调用,它的内部结构如图:

Android StateMachine 类图
StateMachine 架构示意

StateMachine 所实现的状态机是一种分层状态机(Hierarchical State Machine,HSM),分层管理状态和处理消息。HSM 维护一个层次结构,也就是状态树,每一层都有一个或多个状态,这些状态派生自 StateStateMachine 收到的消息会被传递至这些状态,派生一种状态需要实现其 processMessage 方法来完成该状态的消息处理逻辑。

StateMachine 所实现的状态机启动时,会构造一个状态树,固定每种状态及其父状态、子状态的关系,然后将当前状态设置为指定的初始状态,当前状态相当于一个指针,它在这棵状态树上游走。以下面的状态机层次结构为例,设 mS5 为初始状态,构造完成时,当前状态的转移路径为从 mS5 的最远父状态也就是 mP0 开始,依次经过 mP1mS1,直到 mS5

      mP0
     /   \
    mP1   mS0
   /   \
  mS2   mS1
 /   \     \
mS3   mS4   mS5

完成启动后,状态机开始处理消息。当状态机收到一个消息时,首先转给当前状态 mS5,由其 processMessage 方法处理。若子状态不能处理,消息将被转给其父状态,以此类推,状态机沿着状态树逐级上溯,直到找到一个可以处理该消息的状态,比如一条只有 mP1 才能处理的消息,会经过 mS5mS1 再转进 mP1。如果追溯到最远父状态 mP0 依然不能处理,该消息就会被最后集中处理。

状态机内部有一个 Handler,它负责维护消息队列,并完成消息管理和分发,进入状态机的消息可以选择加入这个消息队列的头部或是尾部,每次处理消息 Handler 就从消息队列头部取出一条消息,分发给相应的状态处理。处理消息时,负责处理消息的状态可以标记要切换到的新状态,待消息处理完后,状态机会自动切换过去。

StateMachine 有两个特殊的 StateHaltingStateQuittingState,它们出现在状态机退出的过程中,haltingState 是停止、即将退出的状态,进入这个状态后,将不能再回到普通状态,此时 Handler 依然在运行,所有的消息由 haltedProcessMessage 方法处理;QuitingState 是退出状态,这个状态结束后,状态机线程结束,完全退出。假设当前状态依然是 mS5,停止或退出状态机时,沿着状态树先后退出 mS5mS1mP1mP0,最后如果停止则进入 mHaltingState,如果退出则进入 mQuittingState

二、代码解读

1. 状态机初始化

StateMachine 提供了三个构造方法,三个方法大同小异,构造时如果没有提供 Looper,就使用内部线程的 Looper

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

    initStateMachine(name, looper);
}

StateMachineLooper 所在的线程运行,状态机初始化时使用 Looper 构造一个 SmHandler,用于处理状态机的通讯。

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

StateMachine 是一种 HSM,需要调用 addState 方法指定它的层级关系,为指定的父状态添加子状态,如果不指定父状态,则创建一个孤立的状态节点。状态关系信息存储于 mStateInfo 中。

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

构造完成后,调用 start 方法启动状态机,其内部调用 mSmHandlercompleteConstruction 方法,完成状态机构造的动作。completeConstruction 方法首先遍历状态树,并找到树的最大深度,用于确定状态栈的大小,并构造初始的状态栈,所有动作完成后,发送 SM_INIT_CMD 消息,报告状态机已构造完成。

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

构造状态栈时,先构造临时状态栈,从初始状态开始,上溯状态树,依次放入临时状态栈中,直到最远父状态,最后再将临时状态栈内容以相反的顺序保存至状态栈中。

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

    // Empty the StateStack
    mStateStackTopIndex = -1;

    moveTempStateStackToStateStack();
}

moveTempStateStackToStateStack 方法将临时状态栈按相反顺序保存到状态栈中。

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

SmHandlerLooper 取得 SM_INIT_CMD 消息,调用 handleMessage 进行处理,内部再调用 invokeEnterMethods,以进入到初始状态。

@Override
public final void handleMessage(Message msg) {
    if (!mHasQuit) {
        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 */
        State msgProcessedState = null;
        if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
            /** 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");

        if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
            mSm.onPostHandleMessage(msg);
        }
    }
}

invokeEnterMethods 的参数为一个整数,表示第一个要进入的状态在栈中的索引位置,从该位置开始依次调用每个状态的 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
}

至此,状态机的初始化过程完成。

2. 消息处理

StateMachine 收到的消息由 mSmHandler 负责管理和分发,在 handleMessage 方法内部,收到消息并且判断 StateMachine 已经启动完成时,就会调用 mSmHandlerprocessMsg 方法,processMsg 获取状态栈栈顶——即当前状态的信息,然后调用所对应的 State 对象的 processMessage 方法,在状态内完成消息处理逻辑。若当前状态无法处理该消息,或是处理完后需要父状态继续处理,则继续调用父状态 State 对象的 processMessage 方法,直到消息处理完成,若所有父状态都不能处理,则调用 mSmHandlerunHandledMessage,尝试最后一次消息处理。该方法返回最后处理消息的状态,抑或是 null

private final State processMsg(Message msg) {
    StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
    if (mDbg) {
        mSm.log("processMsg: " + curStateInfo.state.getName());
    }

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

3. 状态切换

每个状态在消息处理时,可以选择切换到指定状态,这个过程通过调用状态机的 transitionTo 方法触发。状态切换完成后,下一个消息将由新的状态处理。可以看到实际上是调用的 mSmHandler.transitionTo 方法。

public final void transitionTo(IState destState) {
    mSmHandler.transitionTo(destState);
}

mSmHandler.transitionTo 仅标记状态切换的目标状态,实际上不会执行切换操作,这是为了在状态切换前,完成包括当前状态和一系列父状态的消息处理逻辑,待消息处理完成并返回到 mSmHandler.handleMessage 方法后再通过调用 performTransitions 方法执行切换。

/** @see StateMachine#transitionTo(IState) */
private final void transitionTo(IState destState) {
    if (mTransitionInProgress) {
        Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +
                mDestState + ", new target state=" + destState);
    }
    mDestState = (State) destState;
    if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
}

performTransitions 方法负责执行状态的切换流程。首先调用 setupTempStateStackWithStatesToEnter 方法来获得当前状态和目的状态的公共父状态,然后调用 invokeExitMethods 方法从当前状态沿状态栈逐级退出到公共父状态,再调用 invokeEnterMethods 方法从公共父状态进入到目的状态,最后调用 moveDeferredMessageAtFrontOfQueue 方法将延时消息列表的消息导入到消息队列头部。若在此过程中,目的状态发生改变,则再次执行,直到最终的状态与目的状态一致。

private void performTransitions(State msgProcessedState, Message msg) {
    /**
      * If transitionTo has been called, exit and then enter
      * the appropriate states. We loop on this to allow
      * enter and exit methods to use transitionTo.
      */
    State orgState = mStateStack[mStateStackTopIndex].state;

    /**
      * Record whether message needs to be logged before we transition and
      * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
      * always set msg.obj to the handler.
      */
    boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);

    if (mLogRecords.logOnlyTransitions()) {
        /** Record only if there is a transition */
        if (mDestState != null) {
            mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
                    orgState, mDestState);
        }
    } else if (recordLogMsg) {
        /** Record message */
        mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
                mDestState);
    }

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

            /**
              * Determine the states to exit and enter and return the
              * common ancestor state of the enter/exit states. Then
              * invoke the exit methods then the enter methods.
              */
            StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
            // flag is cleared in invokeEnterMethods before entering the target state
            mTransitionInProgress = true;
            invokeExitMethods(commonStateInfo);
            int stateStackEnteringIndex = moveTempStateStackToStateStack();
            invokeEnterMethods(stateStackEnteringIndex);

            /**
              * Since we have transitioned to a new state we need to have
              * any deferred messages moved to the front of the message queue
              * so they will be processed before any other messages in the
              * message queue.
              */
            moveDeferredMessageAtFrontOfQueue();

            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.
      */
    if (destState != null) {
        if (destState == mQuittingState) {
            /**
              * Call onQuitting to let subclasses cleanup.
              */
            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);
              */
            mSm.onHalting();
        }
    }
}

setupTempStateStackWithStatesToEnter 方法构造一个临时状态栈,其中保存状态树中目标状态到公共父状态的依赖路径,并返回栈顶的内容,即当前状态和目的状态的公共父状态,如果没有公共父状态,则返回空。

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.
      */
    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;
}

4. 消息传递

StateMachine 传递消息时,最常用的是 sendMessage 方法,有多个 sendMessage 重载,但都大同小异,内部调用 mSmHandlersendMessage 方法。

public void sendMessage(Message msg) {
    // mSmHandler can be null if the state machine has quit.
    SmHandler smh = mSmHandler;
    if (smh == null) return;

    smh.sendMessage(msg);
}

此外,发送消息时还可以调用 deferMessage 方法,该方法传递的消息将等待当前状态被切换后再处理,同样地,内部调用的是 mSmHandlerdeferMessage 方法。

public final void deferMessage(Message msg) {
    mSmHandler.deferMessage(msg);
}

mSmHandlerdeferMessage 方法将消息添加到一个列表中。

private final void deferMessage(Message msg) {
    if (mDbg) mSm.log("deferMessage: msg=" + msg.what);

    /* Copy the "msg" to "newMsg" as "msg" will be recycled */
    Message newMsg = obtainMessage();
    newMsg.copyFrom(msg);

    mDeferredMessages.add(newMsg);
}

在状态切换完成后,会调用 moveDeferredMessageAtFontOfQueue 方法完成消息列表更新,从列表最后的消息开始,依次放入消息队列的头部。

private final void moveDeferredMessageAtFrontOfQueue() {
    /**
      * The oldest messages on the deferred list must be at
      * the front of the queue so start at the back, which
      * as the most resent message and end with the oldest
      * messages at the front of the queue.
      */
    for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
        Message curMsg = mDeferredMessages.get(i);
        if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
        sendMessageAtFrontOfQueue(curMsg);
    }
    mDeferredMessages.clear();
}

5. 状态机退出

状态机退出流程通过 quitquitNow 方法触发。quit 内部调用了 mSmHandlerquit 方法。

public final void quit() {
    // mSmHandler can be null if the state machine is already stopped.
    SmHandler smh = mSmHandler;
    if (smh == null) return;

    smh.quit();
}

mSmHandler.quit 方法发送一个 SM_QUIT_CMD 消息,告知退出,如前文所述,该消息在处理时被传入 processMsg,直接标记切换的目标状态为 QuittingState。该状态不处理任何消息,其 processMessage 只会返回未处理。performTransitions 执行到退出状态的切换后,调用 cleanupAfterQuitting 停止状态机线程并清空状态机信息。

private final void cleanupAfterQuitting() {
    if (mSm.mSmThread != null) {
        // If we made the thread then quit looper which stops the thread.
        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;
}

quitNow 方法与 quit 方法基本一致,只是将 SM_QUIT_CMD 消息放置在消息队列头部,这样状态机将不等待消息队列处理完成便立即退出。

private final void quitNow() {
    if (mDbg) mSm.log("quitNow:");
    sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
}

此外还有停止状态 HaltingState,可通过调用 transitionToHaltingState 切换过去,该状态只是停止普通状态下的消息处理,不会停止状态机线程或清空状态机信息,消息仍可以由 haltedProcessMessage 方法处理。transitionToHaltingState 方法实际调用的是 mSmHandler.transitionTo 方法,传入的参数为 mSmHandler.mHaltingState

public final void transitionToHaltingState() {
    mSmHandler.transitionTo(mSmHandler.mHaltingState);
}

标签:

分类:

更新时间:

留下评论

您的电子邮箱地址并不会被展示。请填写标记为必须的字段。 *

正在加载...