Android state machine StateMachine use example and source code analysis

Keywords: Android Java SDK network

Android frameworks source StateMachine use examples and source analysis

At work, a colleague talked about Android state machine. As a senior Android Engineer, I haven't heard of StateMachine, so I'll take the time to learn.
StateMachine is not a related API in Android SDK. It exists in a Java class in the source code of framework layer. Probably because of this, many application layer developers have not used it.
So let's talk about how StateMachine is used, and then introduce the source code.

  • StateMachine use example
  • StateMachine principle learning

1, StateMachine use example

StateMachine is in the Android framework layer source framework / base / core / Java / COM / Android / internal / util path. To use StateMachine in the application layer, three classes in the corresponding path need to be copied to their own project directory.
The three classes are StateMachine.java, State, and IState

The following is the code example used. I also found this example on the network (after reading the StateMachine source code, I made some simple changes to this example. The following is the changed case):

It is mainly described in the following parts:

  • PersonStateMachine.java case code
  • Used by PersonStateMachine
  • Brief description of the case
  • Case source code download

1.1,PersonStateMachine.java

Create PersonStateMachine to inherit StateMachine class.
Create four states, all of which inherit from State:

  • Default state BoringState
  • Working state
  • Eating state
  • Sleep state

Four message types of state transition are defined:

  • Wake up message
  • Sleepy message MSG? Tired
  • Hungry message
  • State machine stop message MSG? Halting

Here is the complete case code:

public class PersonStateMachine extends StateMachine {

    private static final String TAG = "MachineTest";

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

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

    /**
     * Construction method
     *
     * @param name
     */
    PersonStateMachine(String name) {
        super(name);
        //Join state
        addState(mBoringState, null);
        addState(mSleepState, mBoringState);
        addState(mWorkState, mBoringState);
        addState(mEatState, mBoringState);

        // sleep state is initial state
        setInitialState(mSleepState);
    }

    /**
     * @return Create start person state machine
     */
    public static PersonStateMachine makePerson() {
        PersonStateMachine person = new PersonStateMachine("Person");
        person.start();
        return person;
    }


    @Override
    protected void onHalting() {
        synchronized (this) {
            this.notifyAll();
        }
    }


    /**
     * Definition state: boring
     */
    class BoringState extends State {
        @Override
        public void enter() {
            Log.e(TAG, "############ enter Boring ############");
        }

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

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "BoringState  processMessage.....");
            return true;
        }
    }

    /**
     * Definition state: sleep
     */
    class SleepState extends State {
        @Override
        public void enter() {
            Log.e(TAG, "############ enter Sleep ############");
        }

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

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "SleepState  processMessage.....");
            switch (msg.what) {
                // Receive a wake-up call
                case MSG_WAKEUP:
                    Log.e(TAG, "SleepState  MSG_WAKEUP");
                    // Enter working state
                    transitionTo(mWorkState);
                    //...
                    //...
                    //Send a signal of starvation
                    sendMessage(obtainMessage(MSG_HUNGRY));
                    break;
                case MSG_HALTING:
                    Log.e(TAG, "SleepState  MSG_HALTING");

                    // Transition to suspended state
                    transitionToHaltingState();
                    break;
                default:
                    return false;
            }
            return true;
        }
    }


    /**
     * Definition status: work
     */
    class WorkState extends State {
        @Override
        public void enter() {
            Log.e(TAG, "############ enter Work ############");
        }

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

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "WorkState  processMessage.....");
            switch (msg.what) {
                // I got a hunger signal
                case MSG_HUNGRY:
                    Log.e(TAG, "WorkState  MSG_HUNGRY");
                    // Eating state
                    transitionTo(mEatState);
                    //...
                    //...
                    // Send tired signal
                    sendMessage(obtainMessage(MSG_TIRED));
                    break;
                default:
                    return false;
            }
            return true;
        }
    }

    /**
     * Definition status: eating
     */
    class EatState extends State {
        @Override
        public void enter() {
            Log.e(TAG, "############ enter Eat ############");
        }

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

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "EatState  processMessage.....");
            switch (msg.what) {
                // I got a signal
                case MSG_TIRED:
                    Log.e(TAG, "EatState  MSG_TIRED");
                    // Sleep
                    transitionTo(mSleepState);
                    //...
                    //...
                    // Send end signal
                    sendMessage(obtainMessage(MSG_HALTING));
                    break;
                default:
                    return false;
            }
            return true;
        }

    }
}

1.2 use of PersonStateMachine

// Get state machine reference
PersonStateMachine personStateMachine = PersonStateMachine.makePerson();
// The initial state is SleepState, and the message MSG_WAKEUP is sent
personStateMachine.sendMessage(PersonStateMachine.MSG_WAKEUP);
  • When the SleepState state receives the MSG ﹣ wakeup message, it will execute the processMessage method of the corresponding state
  • In the SleepState class, after the processMessage method receives the MSG ﹣ wakeup message, it executes the transitionTo(mWorkState) method to complete the state transition. Transition to WorkState state.

1.3. Brief description of the case

The dependencies of several states are as follows:

In the construction method, add all States and set the initial state:

PersonStateMachine(String name) {
    super(name);
    //Join state
    addState(mBoringState, null);
    addState(mSleepState, mBoringState);
    addState(mWorkState, mBoringState);
    addState(mEatState, mBoringState);
    
    // sleep state is initial state
    setInitialState(mSleepState);
}

Create and start a state machine by:

public static PersonStateMachine makePerson() {
    PersonStateMachine person = new PersonStateMachine("Person");
    person.start();
    return person;
}

1.4 download case source code

Android? Statemachine case address

2, Realize principle learning

In StateMachine, a thread HandlerThread is opened, and its corresponding Handler is SmHandler. Therefore, the processMessage(Message msg) method of the corresponding state in the above case is executed in the HandlerThread thread.

2.1. Start with the construction method of StateMachine, the corresponding code is as follows:

protected StateMachine(String name) {
    // Create HandlerThread
    mSmThread = new HandlerThread(name);
    mSmThread.start();
    // Get the Looper corresponding to HandlerThread
    Looper looper = mSmThread.getLooper();
    // Initialize StateMachine
    initStateMachine(name, looper);
}
  • In the construction method of StateMachine, a thread HandlerThread is created and started;
  • In the initStateMachine method, the Handler SmHandler corresponding to the HandlerThread thread is created
private void initStateMachine(String name, Looper looper) {
    mName = name;
    mSmHandler = new SmHandler(looper, this);
}
  • In the SmHandler construction method, two states are added to the state machine: a pause state of the state machine, mHaltingState, and an exit state, mQuittingState
private SmHandler(Looper looper, StateMachine sm) {
    super(looper);
    mSm = sm;

    // Add status: pause and exit
    // These two states have no parent state
    addState(mHaltingState, null);
    addState(mQuittingState, null);
}
  • mHaltingState, as the name implies, pauses the state machine. Its corresponding processMessage(Message msg) method returns a value of true, which consumes the message but does not process the message. This causes the state machine state to pause to the mHaltingState state state
  • Mquitingstate state, in which the state machine exits. The Looper corresponding to the HandlerThread thread will exit, the HandlerThread thread will be destroyed, and all the States added to the state machine will be cleared.

2.2. start() method of state machine

After initializing the state machine, start() is the start method of the state machine

public void start() {
    // mSmHandler can be null if the state machine has quit.
    SmHandler smh = mSmHandler;
    // StateMachine is not initialized, why not throw an exception
    if (smh == null) {
        return;
    }
    // Complete the construction of state machine
   smh.completeConstruction();
}
  • As you can see from the above code, there is only one method, completeConstruction(), which is used to complete the construction of the state machine.
private final void completeConstruction() {
    int maxDepth = 0;
    // Loop to judge all States, see which chain is the longest, and get the depth
    for (StateInfo si : mStateInfoHashMap.values()) {
        int depth = 0;
        for (StateInfo i = si; i != null; depth++) {
            i = i.parentStateInfo;
        }
        if (maxDepth < depth) {
            maxDepth = depth;
        }
    }
    // Status stack
    mStateStack = new StateInfo[maxDepth];
    // Temporary state stack
    mTempStateStack = new StateInfo[maxDepth];
    // Initial Stack 
    setupInitialStateStack();

    // Send the message of initialization completion (put the message at the front of the queue)
    sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
}
  • maxDepth is the length of the longest dependency chain in the state machine.
  • mStateStack and mstempstatestack are two stacks implemented by arrays. The maximum length of these two stacks is maxDepth. It is used to store the parent state, parent state, etc. of the current active state and the current active state
  • setupInitialStateStack(); initializes the state and places the current active state on the mStateStack stack.

Next let's talk about how to initialize the stack in the setupInitialStateStack(); method.

private final void setupInitialStateStack() {
    // Get initial state information
    StateInfo curStateInfo = mStateInfoHashMap.get(mInitialState);
    //
    for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
        // Initial state put on temporary stack
        mTempStateStack[mTempStateStackCount] = curStateInfo;
        // All parent states of the current state are placed one level on the stack
        curStateInfo = curStateInfo.parentStateInfo;
    }

    // Empty status stack
    // Empty the StateStack
    mStateStackTopIndex = -1;
    // Temporary stack to state stack
    moveTempStateStackToStateStack();
}
  • Take the state in the case as an example, and put the initialization state into the mTempStateStack stack
  • Put the parent state, parent parent state and parent parent parent state of initialization state into the mTempStateStack stack one by one

  • Then, in the moveTempStateStackToStateStack() method, mstempstatestack is out of stack, mStateStack is in stack, all state information is imported into mStateStack stack, and the mstempstatestack stack is emptied.

So far, initialization is basically completed, but we have left a part of the code without saying:

// Send the message of initialization completion (put the message at the front of the queue)
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
  • Send an initialization completed message to SmHandler.

Next, let's look at the handleMessage(Message msg) method of SmHandler:

public final void handleMessage(Message msg) {

    // Processing message
    if (!mHasQuit) {
        // Save incoming message
        mMsg = msg;
        State msgProcessedState = null;
        // Initialization completed
        if (mIsConstructionCompleted) {
        // ..
        }
        // Initialization completed message received
        else if (!mIsConstructionCompleted
                && (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
            /** Initial one time path. */
            // Initialization complete
            mIsConstructionCompleted = true;
            // Call the enter method of the state in the stack and set the state in the stack to active
            invokeEnterMethods(0);
        } else {
        // ..
        }
        // Execute Transition
        performTransitions(msgProcessedState, msg);
    }
}
  • After receiving the initialization completed message, mIsConstructionCompleted = true; the corresponding flag bit changes
  • Execute the invokeEnterMethods method to set all the states in the mStateStack stack to the active state, and execute the enter() method of the states in the stack in the order of the parent - > child
  • Performtransition (MSG processedstate, MSG); when start(), none of its contents will be executed, so it will not be introduced first.

The method body of invokeEnterMethods is as follows:

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;
    }
}
  • As you can see, it sets all States in the mStateStack stack as active states, and executes the enter() method of states in the stack in the order of parent - > child

This start() completes, and the final mStateStack stack state is also shown in the figure above.

2.3 state transformation

Take the code in the case as an example:

// Get state machine reference
PersonStateMachine personStateMachine = PersonStateMachine.makePerson();
// The initial state is SleepState, and the message MSG_WAKEUP is sent
personStateMachine.sendMessage(PersonStateMachine.MSG_WAKEUP);
  • By calling SendMessage (personstatemachine. MSG? Wakeup); method, a message is sent to SmHandler to trigger state transformation.
  • It can be said that sendMessage(PersonStateMachine.MSG_WAKEUP); message is the fuse of state transformation.

Next, take a look at the handleMessage(Message msg) method of SmHandler again:

public final void handleMessage(Message msg) {
    // Processing message
    if (!mHasQuit) {
        // Save incoming message
        mMsg = msg;
        State msgProcessedState = null;
        // Initialization completed
        if (mIsConstructionCompleted) {
            // Status of processing messages
            msgProcessedState = processMsg(msg);
        }
        // Initialization completed message received
        else if (!mIsConstructionCompleted
                && (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
            // Initialization complete
            mIsConstructionCompleted = true;
            // Call the enter method of the state in the stack and set the state in the stack to active
            invokeEnterMethods(0);
        } else {
            throw new RuntimeException("StateMachine.handleMessage: "
                    + "The start method not called, received msg: " + msg);
        }
        // Execute Transition
        performTransitions(msgProcessedState, msg);
    }
}
  • Because initialization is complete, the code goes directly to the processMsg(msg); method.

Let's look at processMsg(msg); method:

private final State processMsg(Message msg) {
    // Current state found in stack
    StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
    // Exit message or not
    if (isQuit(msg)) {
        // Convert to exit status
        transitionTo(mQuittingState);
    } else {
        // If the status returns true, the status can be processed
        // If the status returns false, it cannot be processed
        while (!curStateInfo.state.processMessage(msg)) {
            // Parent state of current state
            curStateInfo = curStateInfo.parentStateInfo;
            // Parent state is not null
            if (curStateInfo == null) {
                // Callback to unprocessed message method
                mSm.unhandledMessage(msg);
                break;
            }
        }
    }
    // After the message is processed, the current status information is returned
    // If the message is not processed, its parent state is returned for processing, and the parent state of the processed message is returned
    return (curStateInfo != null) ? curStateInfo.state : null;
}
  • The code will go straight to while (!curStateInfo.state.processMessage(msg))
    Execute the processMessage(msg) method of the uppermost state in the mStateStack. In the case, the state is SleepState
  • Here
    If the processMessage(msg) method of the state in the mStateStack returns true, it means that it consumes the message;
    If it returns false, the message will not be consumed, and the message will continue to be delivered to its parent state;
  • Finally, it will return and consume the status of the message.

Here, the state of the upper layer of the stack pair is SleepState. So let's look at the corresponding processMessage(msg) method.

public boolean processMessage(Message msg) {
    switch (msg.what) {
        // Receive a wake-up call
        case MSG_WAKEUP:
            // Enter working state
            transitionTo(mWorkState);
            //...
            //...
            //Send a signal of starvation
            sendMessage(obtainMessage(MSG_HUNGRY));
            break;
        case MSG_HALTING:
        // ...
            break;
        default:
            return false;
    }
    return true;
}
  • In the processMessage(Message msg) method of the SleepState state state, after receiving the MSG ﹣ wakeup message, it will call the transitionTo(mWorkState); method to set the target state to mWorkState.

Let's look at the transitionTo(mWorkState); method:

private final void transitionTo(IState destState) {
    mDestState = (State) destState;
}
  • As you can see, the transitionTo(IState destState) method is just a simple state assignment.

Next, we return to the handleMessage(Message msg) method of SmHandler:

  • The code will execute into the performtransition (MSG processedstate, MSG); method of SmHandler.handleMessage(Message msg).
  • The parameter msgProcessedState passed in here is mSleepState.
private void performTransitions(State msgProcessedState, Message msg) {
    // current state
    State orgState = mStateStack[mStateStackTopIndex].state;
    // ...
    // Target state
    State destState = mDestState;
    if (destState != null) {
        while (true) {
            // Target state in temp stack
            // The parent state of the target state is passed to the next level as a parameter
            StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
            // All substates of the commonStateInfo state are destacked
            invokeExitMethods(commonStateInfo);
            // Target state push
            int stateStackEnteringIndex = moveTempStateStackToStateStack();
            // Active in stack state
            invokeEnterMethods(stateStackEnteringIndex);
            //...
            moveDeferredMessageAtFrontOfQueue();

            if (destState != mDestState) {
                // A new mDestState so continue looping
                destState = mDestState;
            } else {
                // No change in mDestState so we're done
                break;
            }
        }
        mDestState = null;
    }
    // ...
}
  • The parameter msgProcessedState passed in the above method is mSleepState
  • destState in method target state is mWorkState

At this moment, the execution diagram of the content in the performtransition (state msgprocessedstate, message MSG) method is as follows:

A. Target state is put into mTempStateStack queue
// Target state in temp stack
// The parent state of the target state is passed to the next level as a parameter
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
  • 1. Put the WorkState state into the mTempStateStack stack
  • 2. Put the inactive parent state of the WorkState state one by one into the mTempStateStack stack stack
  • 3. Because the parent state of the WorkState is BoringState, which is an active state, only the WorkState is placed in the mTempStateStack stack
  • 4. Returns the active parent state BoringState

The execution diagram of the above code is as follows:

B. Substate destacking of commonStateInfo state in mStateStack

commonStateInfo is the return parameter of the setupTempStateStackWithStatesToEnter(destState); method. This is BoringState

// All substates of the commonStateInfo state are destacked
invokeExitMethods(commonStateInfo);
  • 1. BoringState is passed in as an argument to invokeExitMethods(commonStateInfo); method
  • 2. Its method content is to Backstack all sub states of the BoringState state

The execution diagram of the above code is as follows:

C. Mstempstatestack all States out of stack, mStateStack in stack
// Target state push
int stateStackEnteringIndex = moveTempStateStackToStateStack();
// Active in stack state
invokeEnterMethods(stateStackEnteringIndex);
  • In the moveTempStateStackToStateStack method: mstempstatestack all States are out of the stack, mStateStack is in the stack
  • Invokeentermethods (statestackenterengindex); method, set the newly added state to the active state; and call its corresponding enter() method.

The final stack state is:

The source code explanation of StateMachine is finished.
Interested students, or read the source code, I hope this article can provide some help for your source reading.

========== THE END ==========

Posted by Jorge on Mon, 02 Mar 2020 01:57:35 -0800