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.