Case 1.
Case study: our common car, we can use it to drive, can also stop it at the roadside. When it is in the process of driving, it needs to constantly detect the amount of oil, once the amount of oil is insufficient, it will fall into a stop state. And stop the car on the side of the road, need to start ignition, at this time will detect the amount of oil in the car, when the amount of oil is insufficient, the car will need to go to the gas station to refuel.
When we abstract the state and behavior of the car, the state of the car can be:
- STOP STOP
- On the RUN
- Check the oil quantity CHECK_OIL
- Refueling ADDING_OIL
And what we can do to the car can be:
- Parking ACTION_STOP
- Driving ACTION_RUN
- Refueling ACTION_ADD_OIL
We build a two-dimensional table that combines states and actionable behaviors:
2. HSM
We use this state table to build our state reference relationship model:
This state diagram is actually a relatively complex network, which increases exponentially in complexity when building a more complex system. To solve this problem, we need to transform this mesh State Machine into a tree-like Hierarchical State Machine, also called HSM (Hierarchical State Machine). We can transform the above state model into:
In this diagram, STOP is used as the root node and hierarchically as the parent node of other state nodes.
STOP
As an initial state- happened
ACTION_ADD_OIL
Action,STOP
The state becomes 1, 2, 3ADDING_OIL
state - when
ADDING_OIL
It’s over. It happenedACTION_RUN
Action, you need to popADDING_OIL
State, incoming toCHECK_OIL
And pass inRUN
State.
3. [StateMachine] Initialization
The StateMachine is the realization of the Android system provides state of HSM machine, its source in the package. Com Android. Internal. Util. StateMachine provides three constructors, but they are very much the same:
protected StateMachine(String name) {
mSmThread = new HandlerThread(name);
mSmThread.start();
Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
Copy the code
The constructor calls initStateMachine, which needs to pass in a Looper object. All operations on the StateMachine object need to run in the thread of the Looper. The communication is passed through SmHandler objects.
private void initStateMachine(String name, Looper looper) {
mName = name;
mSmHandler = new SmHandler(looper, this);
}
Copy the code
The addState function takes two arguments. The second argument represents the parent node of the first argument: StateMachine is an HSM StateMachine.
protected final void addState(State state, State parent) {
mSmHandler.addState(state, parent);
}
Copy the code
The root node, also known as the initial state node, needs to be specified by the setInitialState function:
protected final void setInitialState(State initialState) {
mSmHandler.setInitialState(initialState);
}
Copy the code
For example, when a node is added by calling StateMachine. AddState, the smHandler. addState function is called:
//code SmHandler
private final StateInfo addState(State state, State parent) {
if (mDbg) {// The debugging function can be statemachine.setdbgInterface Settings Open msm.log ("addStateInternal: E state=" + state.getName() + ",parent="
+ ((parent == null) ? "": parent.getName())); } StateInfo parentStateInfo = null; // StateInfo represents the state node in the HSM treeif(parent ! = null) { parentStateInfo = mStateInfo.get(parent); //mStateInfo is a HashMap objectif(parentStateInfo == null) {add this node without a parent node // Add our parent as it Recursively'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 ! // Throw new RuntimeException(parentStateInfo)) {throw new RuntimeException("state already added"); } stateInfo.state = state; stateInfo.parentStateInfo = parentStateInfo; // Build a parent-child hierarchical relationship stateInfo.active =false;
if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
return stateInfo;
}
Copy the code
MStateInfo is a HashMap
object, and the StateInfo class is an HSM node object used to record State object information, as well as parent node information
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;
}
Copy the code
Following our abstraction of the Car model, we can define a Car state machine:
public class Car extends StateMachine { .... public Car(String name) { super(name); this.addState(mStopState,null); This.addstate (mAddOilState,mStopState); / / mAddOilState as mStopState sub-state enclosing addState (mCheckOilState mStopState); //mCheckOilState as a child of mStopState this.addState(mRunState,mCheckOilState); //mRunState as a child of mCheckOilState this.setInitialState(mStopState); // mStopState is initial}}Copy the code
When we have constructed our tree structure, we can start our StateMachine by using the StateMachine. Start function:
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(); / / call pleteConstruction} SmHandler.comCopy the code
The StateMachine. Start calling SmHandler.com pleteConstruction used to submit before we all operations:
private final void completeConstruction() {
if (mDbg) mSm.log("completeConstruction: E"); /** * Determine the maximum depth of the state hierarchy * so we can allocate the state stacks. */ int maxDepth = 0; // step1for (StateInfo si : mStateInfo.values()) {
int depth = 0;
for(StateInfo i = si; i ! = null; depth++) { i = i.parentStateInfo; }if(maxDepth < depth) { maxDepth = depth; // Find the deepest stack}}if (mDbg) mSm.log("completeConstruction: maxDepth="+ maxDepth); mStateStack = new StateInfo[maxDepth]; mTempStateStack = new StateInfo[maxDepth]; SetupInitialStateStack (); // Save to mStateStack /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */ sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));if (mDbg) mSm.log("completeConstruction: X");
}
Copy the code
According to our HSM model, when the STOP state is the base state, then we extend this state as the bottom of the stack upward, we can get two stacks, which are respectively:
stack1: [STOP,CHECK_OIL,RUN]
stack2: [STOP,ADD_OIL]
Copy the code
Stack1 has a maximum depth of 3 and Stack2 has a maximum depth of 2. Stack1 can then be applied to stack2. The step1 section of the completeConstruction code does just that, finding the largest stack to use for all stack cases.
private final void setupInitialStateStack() {
if (mDbg) {
mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
}
StateInfo curStateInfo = mStateInfo.get(mInitialState);
for(mTempStateStackCount = 0; curStateInfo ! = null; mTempStateStackCount++) { mTempStateStack[mTempStateStackCount] = curStateInfo; curStateInfo = curStateInfo.parentStateInfo; } // Empty the StateStack mStateStackTopIndex = -1; moveTempStateStackToStateStack(); // Copy tempStack backwards to stateStack}Copy the code
MTempStateStack is an intermediate variable that stores mStateStack in reverse order. Let’s say our initial state is RUN. So we need to loop the parent of RUN into mTempStateStack to get:
mTempStateStack :[RUN,CHECK_OIL,STOP]
Copy the code
This time we need to call moveTempStateStackToStateStack function copies it flashbacks to mStateStack object, ensure that the current state RUN in the stack:
mStateStack: [STOP,CHECK_OIL,RUN]
Copy the code
The mStateStackTopIndex variable points to the top of the stack of mStateStack. In this example, the value of mStateStackTopIndex is 2, pointing to the array index where RUN is located.
At this point in the start function call, we have a tree data structure and the initial state set up, and we can then send our instructions to our state machine.
4. [StateMachine] Processes messages
After we construct a state machine through the above means, we can instruct the state machine to process messages. Let’s start by giving our state machine some interfaces for external calls:
public interface ICar {
public void run();
public void stop();
public void addOil();
}
public class Car extends StateMachine implements ICar{
....
}
public void func() {
ICar car = new Car("Ford");
car.addOil();
car.run();
car.stop();
}
Copy the code
When we want to send instructions to our state machine, we need to call sendMessage(…) of the state machine. The android.os.Handler function is the same as the API provided by android.os.Handler. In fact, the state machine handles such messages in a Handler manner, and the SmHandler object mentioned above is actually a subclass of Handler.
public final void sendMessage(int what) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return; smh.sendMessage(obtainMessage(what)); // Send message via Handler}Copy the code
Thus, we can use this function to implement several of our interface methods:
public class Car extends StateMachine implements ICar{
...
public void run() {
this.sendMessage(ACTION_RUN);
}
public void stop() {
this.sendMessage(ACTION_STOP);
}
public void addOil() { this.sendMessage(ACTION_ADD_OIL); }}Copy the code
According to what we know about the Handler class, every time we send a message via handler. sendMessage, the next Handler message will be executed by Looper, Call the handler.handleMessage (Message MSG) method. Since SmHandler inherits from Handler and it overwrites the handleMessage function, the message is finally called back to the SmHandler.handleMessage method after it is sent.
//code SmHandler
public final void handleMessage(Message msg) {
if(! mHasQuit) {if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
/** Save the current message */
mMsg = msg;
/** State that processed the message */
State msgProcessedState = null;
if(mIsConstructionCompleted) { /** Normal path */ msgProcessedState = processMsg(msg); // Handle by current state}else if(! mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) { /** Initial one time path. */ // Execute the initialization operator mIsConstructionCompleted =true; invokeEnterMethods(0); // When called}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"); }}Copy the code
The smHandler. handleMessage function performs the following operations:
- According to the
mHasQuit
Determine whether to exit. If exit, subsequent instructions will not be executed - Determine if initial completion is complete (by variable
mIsConstructionCompleted
) if initialization completes the callprocessMsg
Throws the message to the current state for execution - If it is not initialized and you receive an initialization command
SM_INIT_CMD
An initialization operation will be performed - After the command is executed, run the command
performTransitions
The function is used to convert the current state andmStateStack
Let’s start with the third topic [StateMachine] initialization and look at step 3. SM_INIT_CMD instructions issued at SmHandler.com pleteConstruction function:
//code SmHandler
private final void completeConstruction() {... /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */ sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)); . }Copy the code
When an initialization message is processed, mIsConstructionCompleted is set to true to tell the state machine that it has been initialized and that it is ready for the state to process the message. Then an invokeEnterMethods function is called. The purpose of this function is to call back the enter method for all active states in the current mStateStack stack. And set the inactive state to active:
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
mStateStack[i].state.enter();
mStateStack[i].active = true; }}Copy the code
Thus, if our initial state is STOP, we can see in background print:
//console output:
output: [StateMachine] StopState enter
Copy the code
If our initial state is RUN, we can see:
//console output:
output: [StateMachine] StopState enter
output: [StateMachine] CheckOilState enter
output: [StateMachine] RunState enter
Copy the code
This is the process of processing the initialization message. At this point, the initialization process is complete. Moving on to the post-initialization logic, once the initialization has finished, the incoming messages will be submitted to the appropriate state for execution through the processMsg function.
private final State processMsg(Message msg) { StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; // Get the current status nodeif(isQuit(MSG)) {// Check whether the current message is an exit message transitionTo(mQuittingState); }else {
while(! CurStateInfo. State. Intrinsic processMessage (MSG)) {/ / when the state does not handle the news, will be entrusted to the parent state handling curStateInfo = curStateInfo. ParentStateInfo;if(curStateInfo == null) { mSm.unhandledMessage(msg); // Call unhandledMessage when there is no state to process the current messagebreak; }}}return(curStateInfo ! = null) ? curStateInfo.state : null; }Copy the code
ProcessMsg determines whether it is currently an exit message, and if isQuit is valid, it will go to the mQuittingState state. We will look at how to perform the exit operation later, but we have an idea of this for the moment. When it is not an exit message, it is assigned to the current state. If the current state cannot handle it, it is delegated to the parent state. For example, our initial state is RUN. Then the corresponding mStateStack is:
[STOP,CHECK_OIL,RUN]
Copy the code
Our test code for the state is:
private class BaseState extends State {
@Override
public void enter() {
log(" enter "+this.getClass().getSimpleName());
super.enter();
}
@Override
public void exit() {
log(" exit "+this.getClass().getSimpleName());
super.exit();
}
}
public class StopState extends BaseState {
@Override
public boolean processMessage(Message msg) {
log("StopState.processMessage");
returnHANDLED; Public class CheckOilState extends BaseState {@override public Boolean processMessage(Message MSG) {log("CheckOilState.processMessage");
returnNOT_HANDLED; } public class RunState extends BaseState {}Copy the code
We send a message to the state machine Car:
Car car = new Car();
car.sendMessage(0x01);
Copy the code
We will print log in the background:
--> Enter StopState --> Enter CheckOilState --> Enter RunState // Initialization ends -->[StateMachine]:handleMessage 1 -- > CheckOilState. Intrinsic processMessage / / the run state does not handle, throw checkoil state - > StopState. Intrinsic processMessage / / checkoil state does not handle, thrown to stop stateCopy the code
Of course, if you don’t want the message to be HANDLED by the delegate, you can call processMessage in the initial state and return the HANDLED constant so that it doesn’t have to be HANDLED.
5. [StateMachine] Status changes
Normally, we perform a State transition inside state. processMessage by calling the transitionTo function, which simply stores the State you want to transition into a temporary object:
protected final void transitionTo(IState destState) {
mSmHandler.transitionTo(destState);
}
private final void transitionTo(IState destState) {
mDestState = (State) destState;
}
Copy the code
The actual state transition will occur after the smHandler. handleMessage function executes:
public final void handleMessage(Message msg) {
if(! mHasQuit) { ... performTransitions(msgProcessedState, msg); // Change status}}Copy the code
Here the performTransitions function is called to complete the transitions. If the transitions are from the RUN state, the transitions will be made when the transition to the ADD_OIL state is required:
/** initial: mStateStack: [ STOP,CHECK_OIL,RUN] */ private void performTransitions(State msgProcessedState, Message msg) { State orgState = mStateStack[mStateStackTopIndex].state; //orgState Records the current status. State destState = mDestState; DestState Records the destination state to be changedif(destState ! = null) {while (true) { StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); // invokeExitMethods(commonStateInfo) invokeExitMethods(commonStateInfo) / / from the stack to the commonStateInfo (does not contain the location of the exit operation on int stateStackEnteringIndex = moveTempStateStackToStateStack (); invokeEnterMethods(stateStackEnteringIndex); moveDeferredMessageAtFrontOfQueue(); // Put the Deferred message in the queue header for priority executionif(destState ! = mDestState) { destState = mDestState; }else {
break;
}
}
mDestState = null;
}
if(destState ! = null) {if (destState == mQuittingState) {
//TODO clean
} else if (destState == mHaltingState) {
//TODO halt
}
}
}
Copy the code
This code is executed, the first to find the target node and the common ancestor of the current node, this is by calling setupTempStateStackWithStatesToEnter calls. The name of the StateMachine function is known by its name. *Temp* means that the intermediate variable mTempStateStack is used in this function. *ToEnter indicates that the State. Enter operation needs to be performed on the added State.
private final StateInfo setupTempStateStackWithStatesToEnter(State destState) { mTempStateStackCount = 0; // Reset mTempStateStack StateInfo curStateInfo = mStateInfo.get(destState);do {
mTempStateStack[mTempStateStackCount++] = curStateInfo;
curStateInfo = curStateInfo.parentStateInfo;
} while((curStateInfo ! = null) && ! curStateInfo.active); // Find the first active state node.return curStateInfo;
}
Copy the code
SetupTempStateStackWithStatesToEnter function is to make the target node is copied to the stack mTempStateStack variable, then will eventually intersection nodes return. This function is executed with at least one destState element. Just from the RUN – > ADD_OIL example, setupTempStateStackWithStatesToEnter will return to STOP state, mTempStateStack for:
mTempStateStack: {ADD_OIL}
Copy the code
We go back to the process performTransitions setupTempStateStackWithStatesToEnter finish, will perform invokeExitMethods function.
private final void invokeExitMethods(StateInfo commonStateInfo) {
while((mStateStackTopIndex >= 0) && (mStateStack[mStateStackTopIndex] ! = commonStateInfo)) { State curState = mStateStack[mStateStackTopIndex].state; curState.exit(); mStateStack[mStateStackTopIndex].active =false; mStateStackTopIndex -= 1; }}Copy the code
This function is equivalent to unstacking non-CommonStateInfo from the mStateStack stack.
mStateStack: {STOP,CHECK_OIL,RUN} ->
invokeExitMethods(STOP) ->
mStateStack: {STOP}
Copy the code
After perform the stack, only need will we just build mTempStateStack copied to mStateStack can build a new state of stack, and the operation is done by moveTempStateStackToStateStack function, And moveTempStateStackToStateStack we just said, is actually to mTempStateStack reverse assignment to mStateStack. Thus, we build a new mStateStack:
mStateStack: {STOP,ADD_OIL}
Copy the code
At this point, we build a new stack of states, which is equivalent to switching states. PerformTransitions after the execution moveTempStateStackToStateStack, call invokeEnterMethods function, perform the active state of the enter method. After performing moveDeferredMessageAtFrontOfQueue put through message queue cache deferMessage function in the message queue Handler’s head:
. int stateStackEnteringIndex = moveTempStateStackToStateStack(); invokeEnterMethods(stateStackEnteringIndex); moveDeferredMessageAtFrontOfQueue(); // Put the Deferred message in the queue header for priority executionif(destState ! = mDestState) { destState = mDestState; }else {
break; }...Copy the code
When we have completed the transitions, we need to handle two special states, which are judged at the end of the performTransitions function:
1. HaltingState
2. QuittingState
Copy the code
6. [StateMachine] exits the StateMachine
To exit the StateMachine, StateMachine provides several methods:
- Quit: Exits and clears all messages in the message queue
- QuitNow: Discards messages in the message queue and performs exit and cleanup operations directly
- TransitionToHaltingState: ditching of messages in the queue, direct execution quit, don’t clean up
From the above statement, quit is more secure than HALT operations. The intercept and stop methods for Thread are similar and understandable. As we said above, the HaltingState and QuittingState are judged and executed at the end of the performTransitions function, so let’s look at the code:
if(destState ! = null) {if(destState == mQuittingState) { mSm.onQuitting(); cleanupAfterQuitting(); }}else if(destState == mHaltingState) { mSm.onHalting(); }} private final voidcleanupAfterQuitting() {
if(mSm.mSmThread ! = null) { getLooper().quit(); Msm. mSmThread = null; } /* Empty data */ msm.msmHandler = null; mSm = null; mMsg = null; mLogRecords.cleanup(); mStateStack = null; mTempStateStack = null; mStateInfo.clear(); mInitialState = null; mDestState = null; mDeferredMessages.clear(); mHasQuit =true;
}
Copy the code
When destState == mQuittingState is set, the statemachine. oncontract function is called back, and then the cleanupAfterFormspspmust operation is performed. DestState == mHaltingState If destState == mHaltingState, the StateMachine performs no halting operations and exits by calling the onHalting function.
7. To summarize
Android inside this StateMachine StateMachine in a lot of source code are involved, the code is very simple, there is no too much difficulty, I hope the above summary can help you understand the meaning of StateMachine source code, and can based on it, develop more personalized functions