Android StateMachine

作者: GrayMonkey | 来源:发表于2018-01-07 23:57 被阅读338次

    前言

    最近在了解WIFI模块时,发现WifiController类继承至StateMachine,而WifiController是WIFI状态切换的重要类,了解StateMachine的工作流程是很有必要的。
    StateMachine在状态机的类别中属于有限状态机(Finite state machine),简称FSM,属于状态设计模式中Context环境类,适用于需要在复杂状态与业务之间进行切换的场景,如游戏当中人物的走、跑、攻击,会经常在这几个状态之中进行切换,运用状态机能保证项目的可拓展性,提高可读性。

    基本使用

    首先声明SDK当中StateMachine的包路径为:
    com.android.internal.util.StateMachine
    且该类是被标注为隐藏类,我们是无法直接使用的,为了能体验下状态机的使用,我们需要将其相关类(IState、State、StateMachine)直接复制到我们的测试Demo中,当然复制完成需要稍微调整下,不然编译不通过,如下所示:


    将SDK中的源码复制到项目中

    StateMachine的基本使用必须按如下四个步骤进行,缺一不可:

    1. 继承StateMachine,StateMachine类的构造函数是Protect访问权限,所以只能通过继承实现实例化
    2. 通过addState方法构造状态层次结构(树形结构,可多棵),状态层次结构根据状态转移图构建,各种状态需要继承State类,实现自己相应业务逻辑
    3. 通过setInitialState设置初始状态
    4. 调用start方法启动状态机

    其他常用API如下表所示:

    Method Description
    quit() 停止状态机,会进入QuttingState
    sendMessage(Message msg) 发送一个消息,供各状态处理
    deferMessage(Message msg) 发送一个延迟消息,在下一次状态转换时,才会被放入消息队列
    transitionTo(IState state) 转移至相应状态
    transitionToHaltingState() 进入HaltingState
    /**
     * Created by graymonkey on 18-1-6.
     * StateMachine类的构造函数是Protect访问权限,所以只能通过继承实现实例化
     */
    
    public class TestStateMachine extends StateMachine {
        public TestStateMachine(String name) {
            super(name);
            constructStatesHierarchy();
        }
        /**
         * 构造状态层次结构(树形结构,可多棵)
         */
        private void constructStatesHierarchy(){
     //step 2
            //构造第一棵树形层次结构
            State s1 = new S1();
            State s2 = new S2();
            State p1 = new P1();
            addState(s1,p1);
            addState(s2,p1);
            //构造第二棵树形层次结构
            State p2 = new P2();
            addState(p2);
     //step 3
            setInitialState(s1);
     //step 4
            start();
        }
    
    }
    

    经过上面的代码,则该状态机的状态层次结构如下图所示,关于多出来的HaltingState、QuittingState见后文分析。

    Demo状态机层次结构

    关于StateMachine的基本使用在SDK中首部注释中已经说的很详细了,下面直接进入源码看看其实现,同时捋一捋工作流程。
    Tips:Demo需要在API24以上才能运行
    Demo下载地址

    源码分析—初始化准备

    StateMachine构造函数
     /**
         * Constructor creates a StateMachine with its own thread.
         *
         * @param name of the state machine
         */
        protected StateMachine(String name) {
            mSmThread = new HandlerThread(name);
            mSmThread.start();
            Looper looper = mSmThread.getLooper();
    
            initStateMachine(name, looper);
        }
    
     /**
         * Initialize.
         *
         * @param looper for this state machine
         * @param name of the state machine
         */
        private void initStateMachine(String name, Looper looper) {
            mName = name;
            mSmHandler = new SmHandler(looper, this);
        }
    

    从构造函数来看,StateMachine内部开启了一个HandlerThread,并且通过SmHandler发送消息给HandlerThread。

    SmHanlder
     private static class SmHandler extends Handler {
      ......
      /** Stack used to manage the current hierarchy of states */
            //由mTempStateStack进行逆序反向操作得到
            private StateInfo mStateStack[];
    
            /** Top of mStateStack */
            private int mStateStackTopIndex = -1;
    
            /** A temporary stack used to manage the state stack */
            private StateInfo mTempStateStack[];
    
            /** The top of the mTempStateStack */
            private int mTempStateStackCount;
    
            /** State used when state machine is halted */
            //空闲状态,当其他State都处理完毕,就会进入该状态
            private HaltingState mHaltingState = new HaltingState();
    
            /** State used when state machine is quitting */
            //退出状态,停用状态机进入的状态
            private QuittingState mQuittingState = new QuittingState();
    
            /** Reference to the StateMachine */
            private StateMachine mSm;
          
           /** The map of all of the states in the state machine */
            //用一个HashMap来存储状态机的状态层次结构
            private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
    
            /** The initial state that will process the first message */
            private State mInitialState;
    
            /** The destination state when transitionTo has been invoked */
            private State mDestState;
        ......
    }
    

    以上代码只是列出了SmHandler的关键成员变量,SmHandler属于StateMachine的私有静态内部类,其中StateInfo、HaltingState、 QuittingState属于SmHandler的私有内部类,留意加了中文注释的成员变量,后文会用到。

    SmHandler构造函数
          /**
             * Constructor
             *
             * @param looper for dispatching messages
             * @param sm the hierarchical state machine
             */
            private SmHandler(Looper looper, StateMachine sm) {
                super(looper);
                mSm = sm;
                //添加空闲状态与退出状态
                addState(mHaltingState, null);
                addState(mQuittingState, null);
            }
    

    可见,在SmHandler实例化的时候,就会向其哈希状态表mStateInfo当中添加空闲状态与退出状态,这就是前文多出来的HaltingState、QuttingState的原因。
    注意:此处addState方法并非调用的是StateMachine的addState方法,而是SmHandler自己的方法,实际上StateMachine的addState方法调用也是SmHandler的addState方法。

    //StateMachine类的addState方法
        /**
         * Add a new state to the state machine
         * @param state the state to add
         * @param parent the parent of state
         */
        public final void addState(State state, State parent) {
            mSmHandler.addState(state, parent);
        }
    

    下面进入SmHandler#addState方法看看

           /**
             * Add a new state to the state machine. Bottom up addition
             * of states is allowed but the same state may only exist
             * in one hierarchy.
             *允许自下向上添加,即添加一个State可以同时添加起父状态,但是同一个
             *状态不能同时拥有2个父状态,会抛出异常
             *
             * @param state the state to add
             * @param parent the parent of state
             * @return stateInfo for this state
             */
            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.
          //异常校验,如果一个State已经有一个父状态,
          //再添加一个父状态则会抛出异常
                if ((stateInfo.parentStateInfo != null)
                        && (stateInfo.parentStateInfo != parentStateInfo)) {
                    throw new RuntimeException("state already added");
                }
              //留意,StateInfo的关键数据结构,后续会用到
                //当前状态
                stateInfo.state = state;
                //当前状态的父节点信息StateInfo类型
                stateInfo.parentStateInfo = parentStateInfo;
                //当前状态是否激活,调用State#enter方法后会激活置为true
                stateInfo.active = false;
                if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
                return stateInfo;
            }
    

    该方法主要是向HashMap<State,StateInfo> mStateInfo添加元素,StateInfo当中存有当前节点的父节点信息,通过Key-Value组合来表示状态机的状态层级结构。
    注意:如果一个State已经拥有了父节点(State类型),再添加另一个新的父节点程序会抛出异常,如果需要更换父节点需要先解绑(置空)之前的父节点再添加新的父节点,错误层级结构如下图所述。

    错误的层次结构

    看完了addState方法,紧跟上文的基本使用步骤,我们来看看StateMachine#setInitialState方法,同addState方法一样,实际上调用也是SmHandler#setInitialState方法,直接进入SmHandler# setInitialState方法

      /** @see StateMachine#setInitialState(State) */
            private final void setInitialState(State initialState) {
                if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
                mInitialState = initialState;
            }
    

    逻辑很简单,就是对SmHandler的成员变量mInitialState进行赋值操作。
    小结:至此,已经分析完基本使用步骤前三步骤的源码,这三个步骤的目的很明显就是对状态机进行一些初始化工作:

    1. 通过addState函数初始化状态机的状态层次结构,该层次结构由SmHandler中的HashMap<State,StateInfo> mStateInfo来存储表示。
    2. 通过setInitialState方法设置初始状态

    源码分析—工作流程

    紧跟上文基本使用步骤,我们进入第四步StateMachine#start方法,来了解状态机的工作流程。

         * Start the state machine.
         */
        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

       /**
             * Complete the construction of the state machine.
             */
            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.
                 *回溯遍历所有State节点,得到最大的树形结构深度
                 */
                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");
            }
    

    我们Demo的树状层级结构的深度应该为2,第一棵树的深度最大,下面进入setupInitialStateStack()看看是如何初始化状态栈的。

      /**
             * Initialize StateStack to mInitialState.
             */
            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;
                }
    
                //逻辑上清空mStateStack,通过将栈顶Index设置为-
                mStateStackTopIndex = -1;
                //初始化 mStateStack,就是将mTempStateStack逆序赋值给mStateStack
                moveTempStateStackToStateStack();
            }
    
     /**
             * Move the contents of the temporary stack to the state stack
             * reversing the order of the items on the temporary stack as
             * they are moved.
             *从mStateStack的当前的栈顶index开始,添加mTempStateStack的出栈元素
             * @return index into mStateStack where entering needs to start
             */
            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;
            }
    

    可见先初始化mTempStateStack,根据我们之前设置的初始化状态往上回溯直至根节点,即初始化状态(StateInfo类型)先入栈,然后是其父节点、祖父节点入栈,以此类推直到根节点入栈;接着通过moveTempStateStackToStateStack()方法初始化mStateStack,即mTempStateStack初始状态位于栈底,mStateStack初始状态位于栈顶。以我们的Demo状态层级结构为例,此时2个状态栈的内容应该如下:


    状态栈

    接下来通过SmHandler#sendMessageAtFrontOfQueue发送SM_INIT_CMD消息,我们进入SmHandler#handleMessage方法看看是如何处理的。

     /**
             * Handle messages sent to the state machine by calling
             * the current state's processMessage. It also handles
             * the enter/exit calls and placing any deferred messages
             * back onto the queue when transitioning to a new state.
             */
            @Override
            public final void handleMessage(Message msg) {
                if (!mHasQuit) {
           //如果收到的消息既不是初始化也不是退出,那么就会调用StateMachine的onPreHandleMessage方法,
           //进行消息预处理
                    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 */
                    /**我们通过start方法首次进入肯定是走else if分支,完成初始化命令
                    后续收到消息才会走if分支进行消息处理*/
                    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;
                       //mStateStack从栈底到栈顶,依次调用相应State的enter方法,先父亲后儿子无可厚非
                       //eg. P1.enter -> S1.enter
                        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");
       //如果收到的消息既不是初始化也不是退出,那么就会调用StateMachine的onPostHandleMessage方法
       //表示消息处理完毕,可以转换到下一个状态
                    if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
                        mSm.onPostHandleMessage(msg);
                    }
                }
            }
    
           /**
             * 从指定起始索引stateStackEnteringIndex到栈顶,依次调用相应State的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
            }
    

    StateMachine的onPreHandleMessage()和onPostHandleMessage()通过继承覆写这2个方法我们可以进行消息的预处理与结束处理,默认是空实现。
    下面进入SmHandler#processMsg方法看看是如何处理后续收到的消息

      /**
             * Process the message. If the current state doesn't handle
             * it, call the states parent and so on. If it is never handled then
             * call the state machines unhandledMessage method.
             * @return the state that processed the message
             */
            private final State processMsg(Message msg) {
                //获取栈顶元素,前文我们提到mStateStack的栈顶元素就是我们设置的初始状态
                StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
                if (mDbg) {
                    mSm.log("processMsg: " + curStateInfo.state.getName());
                }
                //如果消息是StateMachine内部发送的退出消息(调用StateMachine#quit方法),
                //则切换到QuittingState
                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;
            }
    

    SDK的注释说的很明白,当我们调用StateMachine#start完成初始化后,后续通过StateMachine#sendMessage发送的消息会优先分发给我们设置的初始状态进行处理,如果初始状态不能处理(State#processMessage方法返回false),则交给其父节点处理,依次类推,如果所有状态节点都无法处理,则会交给StateMachine#unhandleMessage进行处理。
    看我消息处理函数,下面来看看状态转换处理函数SmHandler#performTransitions

      /**
             * Do any transitions
             * @param msgProcessedState 是上面能处理消息的状态
             */
     private void performTransitions(State msgProcessedState, Message msg) {
           //省略状态机日志相关的代码
                .............
          //mDestState通过StateMachine#translationTo(IState state)赋值
           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");
                /**找到mDestState与当前的初始状态的共同祖先,并设置mTempStateStack
                  *如果不存在共同祖先则返回null*/
                StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
                        // flag is cleared in invokeEnterMethods before entering the target state
                        mTransitionInProgress = true;
                        //从当前初始状态到公共状态依次调用State.exit方法(不含公共状态)
                        //如果没有公共状态,则整个mStateStack中的State.exit都会被调用
                        invokeExitMethods(commonStateInfo);
                        //上文已经分析过这2个方法了,作用是使新的状态层级结构进入激活状态
                        int stateStackEnteringIndex = moveTempStateStackToStateStack();
                        invokeEnterMethods(stateStackEnteringIndex);
                    /**将延迟消息移动到前台处理队列,也就是说通过StateMachine#deferMessage()方法
                      *发送的消息,只有在进行下一次状态转换时才会被加到前台队列,才有机会被执行
                      */
                        moveDeferredMessageAtFrontOfQueue();
                        //此处涉及并发问题,StateMachine#transitionTo并非线程安全,
                        //所以只要mDestState被改变则继续循环
                        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.
                 *扫尾工作,可通过StateMachine#transitionToHaltingState()或
                 *StateMachine#quit/quitNow进入相应状态
                 */
                if (destState != null) {
                    if (destState == mQuittingState) {
                        /**
                         * 调用StateMachine的onQuittiing回调方便子类覆写处理自己的垃圾
                         */
                        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);
                         *进入Halting状态,后续消息会通过HaltingState#processMessage
                         *回调交给StateMachine#haltedProcessMessage进行处理
                         */
                        mSm.onHalting();
                    }
                }
            }
    

    下面看看是如何找到公共祖先的。

      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.
                 */
                //逻辑上清空mTempStateStack
                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;
            }
    

    逻辑很简单,先将destState节点存入mTempStateStack,然后根据destState节点往上回溯,如果该节点为非激活状态则存入mTempStateStack,直到该节点的父节点为激活状态,如果没有公共节点那么往上回溯,返回值肯定是null。
    下面看看SmHandler# invokeExitMethods

      /**
             * Call the exit method for each state from the top of stack
             * up to the common ancestor state.
             *从mStateStack的栈顶依次调用State.exit方法直至公共祖先(不含公共祖先)
             */
            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;
                }
            }
    

    该方法就是从初始状态往上回溯调用exit方法,直至公共祖先(不含公共祖先)。
    至此StateMachine工作流程相关的代码都已经分析完成。

    总结

    假设我们经过前三步基本使用步骤构造的状态层次结构图如下所示:

    状态层次结构

    当我们调用start()方法,进而会调用SmHandler#completeConstruction(),该方法首先会初始化2个状态栈

    状态栈的初始化

    接着发送一个SM_INIT_CMD消息,当SmHandler#handleMessage(),处理这个初始化消息时,会调用SmHandler#invokeEnterMethods(0),依次从mStateStack的栈底(因为传入参数为0)到栈顶调用对应State.enter()方法,即enter方法的调用顺序为P0->P1->S2->S5,并将State.active设置为true,表示已经激活。

    当SmHandler处理通过StateMachine#sendMessage()发送的消息时,会调用SmHandler的processMsg()方法,消息分发逻辑如下:
    消息会优先分配给当前的初始状态,如果该状态不能处理该消息(State#processMessage返回false),则分发给其父节点,以此类推,如果所有状态都不能处理,则分发给StateMachine的unhandleMessage方法进行处理,即消息会从mStateStack的栈顶分发至栈底。

    处理完消息会调用SmHandler#performTransitions方法,进行状态转移,假设我们调用StateMachine#transitionTo(S4),设置S4为目的状态,performTransitions的主要工作逻辑如下:

    1. 调用SmHandler#setupTempStateStackWithStatesToEnter方法找到目的状态与当前初始状态S5(mStateStack的栈顶元素)的公共祖先即P1,同时对mTempStateStack进行重新赋值:先将目的状态S4入栈,然后根据S4往上回溯,如果节点未被激活则入栈,直到找到一个处于激活状态的节点,该节点即是目的状态与当前初始状态的公共祖先。此时,mTempStateStack逻辑上的结构应当如下图所示。
    临时栈
    1. 接着调用SmHandler#invokeExitMethods(commonStateInfo)方法,退出旧的状态,mStateStack依次出栈调用State.exit()方法,直到公共祖先P1(不含公共祖先),即exit的调用顺序为S5->S2,此时mStateStack逻辑上结构如下图所示。
    退出后的栈
    1. 将mTempStateStack整合至mStateStack
    整合
    1. 调用SmHandler# invokeEnterMethods方法,从公共节点之上依次调用State.enter方法,直到栈顶,即新状态的enter调用顺序为S1->S4。

    相关文章

      网友评论

        本文标题:Android StateMachine

        本文链接:https://www.haomeiwen.com/subject/cgdynxtx.html