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 off
Get out of bed
Event 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 mStateStack
mStateStack[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 the
mStateStack
Take out the top state (so far it means C) - call
The State of the intrinsic processMessage
Method, 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