An overview of the

If the process revolves around the error state flow, then the state machine is used. The state machine describes a transaction, with multiple states, and different actions acting on the state leading to the transition of the capture state. There are three key points in this

  • Status: sleep, work, eat
  • What happened: Get up, hungry, tired
  • Action: say the alarm goes offGet out of bedEvent causes state to change from sleep -> Work (can be omitted)

Basically, an event is triggered first, causing the state change, and the alarm triggers the wake up event, causing the state change sleep –> work

Android provides a state machine, In the frameworks source frameworks/base/core/Java/com/android/internal/util, if need to use in the project can take the corresponding to the three classes of copy to the project the StateMachine. Java, the State, IState

Source code analysis

IState

public interface IState {
    /** * Returned by processMessage to indicate the message was processed. * Returned by processMessage to indicate the message was processed. * /
    static final boolean HANDLED = true;
    /** * Returned by processMessage to indicate the message was NOT processed. * Returned by processMessage to indicate the message was NOT processed. * /
    static final boolean NOT_HANDLED = false;
    /** * Called when a state is entered
    void enter(a);
    /** * Called when a state is exited
    void exit(a);
    /**
     * Called when a message is to be processed by the
     * state machine.
     *
     * This routine is never reentered thus no synchronization
     * is needed as only one processMessage method will ever be
     * executing within a state machine at any given time. This
     * does mean that processing by this routine must be completed
     * as expeditiously as possible as no subsequent messages will
     * be processed until this routine returns.
     *
     * @param msg to process
     * @return HANDLED if processing has completed and NOT_HANDLED
     *         if the message wasn't processed.
     */
    boolean processMessage(Message msg);
    /**
     * Name of State for debugging purposes.
     *
     * @returnName of state. Return the name of the state */
    String getName(a);
}
Copy the code

The interface to State, which defines the basic method, State implements IState, and our custom State inherits directly from State

StateMachine

A constructor

  private void initStateMachine(String name, Looper looper) {
        mName = name;
        mSmHandler = new SmHandler(looper, this);
    }
  
    protected StateMachine(String name) {
        mSmThread = new HandlerThread(name);
        mSmThread.start();
        Looper looper = mSmThread.getLooper();
        initStateMachine(name, looper);
    }
   
    public StateMachine(String name, Looper looper) {
        initStateMachine(name, looper);
    }

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

There are three constructors. Looper can be passed in externally, and if not, a new HandlerThread is automatically passed. SmHandler is the inner class of the StateMachine, and its role is equivalent to that of the action mentioned above

addState

  private HashMap<State, StateInfo> mStateInfo =new HashMap<State, StateInfo>();

 private final StateInfo addState(State state, State parent) {
            if (mDbg) {
                Log.d(TAG, "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) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo);
            return stateInfo;
        }


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;
            /** * Convert StateInfo to string */
            @Override
            public String toString(a) {
                return "state=" + state.getName() + ",active=" + active
                        + ",parent=" + ((parentStateInfo == null)?"null": parentStateInfo.state.getName()); }}Copy the code

Like adding a State to a State machine, you can see that the outermost layer uses HashMap to store key=State, value=StateInfo, which stores the current State, active or not, and the parent node of the current State

Suppose there are six states, A->B->C and D->E->F, where C is the parent of B and B is the parent of A

SetInitialState sets the exception to the initialization state

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

 private final void setInitialState(State initialState) {
            if (mDbg) Log.d(TAG, "setInitialState: initialState=" + initialState.getName());
            mInitialState = initialState;
        }
Copy the code

So let’s say that our initial state is C

State machine start

 public void start(a) {
        // mSmHandler can be null if the state machine has quit.
        if (mSmHandler == null) return;
        /** Send the complete construction message */
        mSmHandler.completeConstruction();
    }

  private final void completeConstruction(a) {
            // Get the maximum depth of the state tree first
            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) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth);
	    // Initialize the state stack according to the maximum depth, and the temporary state stack
            mStateStack = new StateInfo[maxDepth];
            mTempStateStack = new StateInfo[maxDepth];
            setupInitialStateStack();
            /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
	    // Send the initialization message to Handler
            sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
            if (mDbg) Log.d(TAG, "completeConstruction: X");
        }

// Fill the mTempStateStack temporary stack based on the initial state
 private final void setupInitialStateStack(a) {
            if (mDbg) {
                Log.d(TAG, "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();
        }

// Then flip mTempStateStack to fill mStateStack
 private final int moveTempStateStackToStateStack(a) {
            int startingIndex = mStateStackTopIndex + 1;
            int i = mTempStateStackCount - 1;
            int j = startingIndex;
            while (i >= 0) {
                if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
                mStateStack[j] = mTempStateStack[i];
                j += 1;
                i -= 1;
            }
            mStateStackTopIndex = j - 1;
            if (mDbg) {
                Log.d(TAG, "moveTempStackToStateStack: X mStateStackTop="
                        + mStateStackTopIndex + ",startingIndex=" + startingIndex
                        + ",Top=" + mStateStack[mStateStackTopIndex].state.getName());
            }
            return startingIndex;
        }

Copy the code

A couple of things were done here

  • Calculate the maximum depth of the state tree
  • Initializes two arrays based on the maximum depth
  • The array is then populated according to the initial State

The state of the array, that is, from mStateStack according to mStateStackTopIndex, is C

Handler handles initialization

public final void handleMessage(Message msg) {
            if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what);
            /** Save the current message */
            mMsg = msg;
            if (mIsConstructionCompleted) {
                /** Normal path */
                processMsg(msg);
            } else if(! mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {/** Initial one time path. */
		// The first initialization goes here
                mIsConstructionCompleted = true;
                invokeEnterMethods(0);
            } else {
                throw new RuntimeException("StateMachine.handleMessage: " +
                        "The start method not called, received msg: " + msg);
            }
	    // Handle the state switch
            performTransitions();
            if (mDbg) Log.d(TAG, "handleMessage: X");
        }

 private final void invokeEnterMethods(int stateStackEnteringIndex) {
            for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
                if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName());
                mStateStack[i].state.enter();
                mStateStack[i].active = true; }}Copy the code

The first initialization does two things

  • First set ‘mIsConstructionCompleted = true;
  • Then you call the invokeEnterMethods(0) method, which, since 0 was passed in, calls all states in mStateStackmStateStack[i].state.enter(); mStateStack[i].active = true;All the activation

Call processMsg if the initialization is complete

   private final void processMsg(Message msg) {
	    // First fetch the top state from mStateStack
            StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
            if (mDbg) {
                Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
            }
            if (isQuit(msg)) {
                transitionTo(mQuittingState);
            } else {
		// Call the processMessage method of the state, calling the parent node if it is not processed, and break out of the loop if the parent is not processed either
                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) {
                        Log.d(TAG, "processMsg: "+ curStateInfo.state.getName()); }}}Copy the code

This does two things

  • First of all, from themStateStackTake out the top state (so far it means C)
  • callThe State of the intrinsic processMessageMethod, calls the parent node if it is not processed, and breaks out of the loop if the parent is not processed either

How do I switch states?

 private final void transitionTo(IState destState) {
            mDestState = (State) destState;
            if (mDbg) Log.d(TAG, "transitionTo: destState=" + mDestState.getName());
        }
Copy the code

Using this method to switch the State, the parameter is the target State, and we see that in the handleMessage, in addition to calling the processMessage method for State, we call performTransitions to handle the State transition. Take a look at this method

 private void performTransitions(a) {
            /** * 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 destState = null;
            while(mDestState ! =null) {
                if (mDbg) Log.d(TAG, "handleMessage: new destination call exit");
                /** * Save mDestState locally and set to null * to know if enter/exit use transitionTo. */
                destState = mDestState;
                mDestState = null;
                /** * 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);
                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(); }}Copy the code

If target state is F, go first 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) {
                Log.d(TAG, "setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
                        + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
            }
            return curStateInfo;
        }
Copy the code

DestState = mTempStateStack; destState = mTemStateStack; destState = mTemStateStack

Then call invokeExitMethods(commonStateInfo);

  private final void invokeExitMethods(StateInfo commonStateInfo) {
            while ((mStateStackTopIndex >= 0) && (mStateStack[mStateStackTopIndex] ! = commonStateInfo)) { State curState = mStateStack[mStateStackTopIndex].state;if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
                curState.exit();
                mStateStack[mStateStackTopIndex].active = false;
                mStateStackTopIndex -= 1; }}Copy the code

If active = false, mStateStack is in the exit state

The next call moveTempStateStackToStateStack

 private final int moveTempStateStackToStateStack(a) {
            int startingIndex = mStateStackTopIndex + 1;
            int i = mTempStateStackCount - 1;
            int j = startingIndex;
            while (i >= 0) {
                if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
                mStateStack[j] = mTempStateStack[i];
                j += 1;
                i -= 1;
            }
            mStateStackTopIndex = j - 1;
            if (mDbg) {
                Log.d(TAG, "moveTempStackToStateStack: X mStateStackTop="
                        + mStateStackTopIndex + ",startingIndex=" + startingIndex
                        + ",Top=" + mStateStack[mStateStackTopIndex].state.getName());
            }
            return startingIndex;
        }
Copy the code

If mTempStateStack is reversed and the mStateStack is filled, the mStateStack state is 0 and the return value is 0

The last call

 private final void invokeEnterMethods(int stateStackEnteringIndex) {
            for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
                if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName());
                mStateStack[i].state.enter();
                mStateStack[i].active = true; }}Copy the code

StateInfo curStateInfo = mStateStack[mStateStackTopIndex] = mStateStack[mStateStackTopIndex

So this is just one of the cases where you go to state F, what if you go to state B? Some regional differences, but basically the same

use


public class MyStateMachine extends StateMachine {


    private static final String TAG = "mmm";

    // Set the status change event
    public static final int MSG_WAKEUP = 1; // Message: wake up
    public static final int MSG_TIRED = 2; // Message: sleepy
    public static final int MSG_HUNGRY = 3; // Message: hungry
    private static final int MSG_HALTING = 4; // State machine pause message

    // Create the state
    private State mBoringState = new BoringState();// Default state
    private State mWorkState = new WorkState(); / / work
    private State mEatState = new EatState(); / / to eat
    private State mSleepState = new SleepState(); / / sleep

    /** * constructor **@param name
     */
    MyStateMachine(String name) {
        super(name);
        // Add state, initialize state
        addState(mBoringState, null);
        addState(mSleepState, mBoringState);
        addState(mWorkState, mBoringState);
        addState(mEatState, mBoringState);

        // Sleep is the initial state
        setInitialState(mSleepState);
    }

    / * * *@returnCreate the start Person state machine */
    public static MyStateMachine makePerson(a) {
        MyStateMachine person = new MyStateMachine("Person");
        person.start();
        return person;
    }


    @Override
    public void onHalting(a) {
        synchronized (this) {
            this.notifyAll(); }}/** * Define state: boring */
    class BoringState extends State {
        @Override
        public void enter(a) {
            Log.e(TAG, "############ enter Boring ############");
        }

        @Override
        public void exit(a) {
            Log.e(TAG, "############ exit Boring ############");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "BoringState processMessage.....");
            return true; }}/** * Define state: sleep */
    class SleepState extends State {
        @Override
        public void enter(a) {
            Log.e(TAG, "############ enter Sleep ############");
        }

        @Override
        public void exit(a) {
            Log.e(TAG, "############ exit Sleep ############");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "SleepState processMessage.....");
            switch (msg.what) {
                // Wake signal received
                case MSG_WAKEUP:
                    Log.e(TAG, "SleepState MSG_WAKEUP");
                    // Enter the working state
                    transitionTo(mWorkState);
                    / /...
                    / /...
                    // Send the hungry signal...
                    sendMessage(obtainMessage(MSG_HUNGRY));
                    break;
                case MSG_HALTING:
                    Log.e(TAG, "SleepState MSG_HALTING");

                    // Switch to the paused state
                    transitionToHaltingState();
                    break;
                default:
                    return false;
            }
            return true; }}/** * Define state: work */
    class WorkState extends State {
        @Override
        public void enter(a) {
            Log.e(TAG, "############ enter Work ############");
        }

        @Override
        public void exit(a) {
            Log.e(TAG, "############ exit Work ############");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "WorkState processMessage.....");
            switch (msg.what) {
                // The hungry signal is received
                case MSG_HUNGRY:
                    Log.e(TAG, "WorkState MSG_HUNGRY");
                    // The state of eating
                    transitionTo(mEatState);
                    / /...
                    / /...
                    // Send tired signal...
                    sendMessage(obtainMessage(MSG_TIRED));
                    break;
                default:
                    return false;
            }
            return true; }}/** * Define state: eat */
    class EatState extends State {
        @Override
        public void enter(a) {
            Log.e(TAG, "############ enter Eat ############");
        }

        @Override
        public void exit(a) {
            Log.e(TAG, "############ exit Eat ############");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "EatState processMessage.....");
            switch (msg.what) {
                // The signal is received
                case MSG_TIRED:
                    Log.e(TAG, "EatState MSG_TIRED");
                    / / sleep
                    transitionTo(mSleepState);
                    / /...
                    / /...
                    // Send the end signal...
                    sendMessage(obtainMessage(MSG_HALTING));
                    break;
                default:
                    return false;
            }
            return true; }}}Copy the code

call

 	// Get the state machine reference
        MyStateMachine personStateMachine = MyStateMachine.makePerson();
        // The initial state is SleepState and the message MSG_WAKEUP is sent
        personStateMachine.sendMessage(MyStateMachine.MSG_WAKEUP);
Copy the code

The log

2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ enter Boring ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ enter Sleep ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: SleepState  processMessage.....
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: SleepState  MSG_WAKEUP
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ exit Sleep ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ enter Work ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: WorkState  processMessage.....
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: WorkState  MSG_HUNGRY
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ exit Work ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ enter Eat ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: EatState  processMessage.....
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: EatState  MSG_TIRED
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ exit Eat ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ enter Sleep ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: SleepState  processMessage.....
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: SleepState  MSG_HALTING
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ exit Sleep ############
2021- 08 -12 18:20:03.137 6035-8981/com.example.myapplication E/mmm: ############ exit Boring ############
Copy the code

The most important thing here is to distinguish between states and events. The first event that triggers the state change, the alarm that triggers the wake up event, the state change sleep -> work

First, we add all the states to the state machine, and then we set the initial state to Sleep, and then we call start

So start the Sleep state and its parent to join the stack, then call enter, then call personStateMachine. SendMessage (MyStateMachine. MSG_WAKEUP); In this case, sendMessage means both actions, myStatemachine.msg_wakeup means events, and SleepState receives the event, triggering a state change transitionTo(mWorkState);

So the current state SleepState only accepts the MSG_WAKEUP event, and if it’s something else, the current state doesn’t accept it, so it doesn’t change the state, so it’s sleeping, it triggers the event to eat, it doesn’t eat while sleeping, so it’s an invalid event