The State Machine is defined here as a machine state architecture that processes messages and can have ordered states.
A state is a State object and enter / exit / getName option states. The enter / exit methods are the equivalent of construction and destruction in object-oriented programming and are used to perform the initialization and cleaning of respective states. The getName method returns the name of the state; the default implementation returns the class name. It may be desirable for the getName method to return an instance of state instead of a name in some separate cases if a separate state class has many instances.
When a state machine is created, addState is used to build the architecture and setInitialState is used to indicate this is the initialization state. After the initialization process, the programmer calls start which will initialize and start a state machine. SateMachine’s first action is to call enter for all architecture’s initialization states, starting at its largest parent. The enter call will be executed in the context of the StateMachine Handler, not in the context of where it was called to start, and they will be called before any messages processed. For example, in the simple state machine below, mP1.enter will be called and then mS1.enter. Finally, messages sent to the state machine will be processed by the current state; In our simple state machine below that could be the mS1.processMessage initialization.
1 2 3 4 |
mP1 / mS2 mS1 ----> initial state |
After the state machine is created and run, messages are sent to a state machine using sendMessage and messages created using obtainMessage. When a state machine receives a message, the current state’s processMessage method is called. In the above example mS1.processMessage will be called first. The state can use transitionTo to change the current state to a new state.
Each state in the state machine can have no or one parent. If a child state cannot process a message, it may have to be handled by its parent by returning false or NOT_HANDLE. If a message is not processed by a child state or any of its ancestors, the unhandledMessage is called to send a last chance to the state machine to process that message.
When all processing completes a state machine can choose to call transitionToHaltingState. When the current processingMessage returns, the state machine moves to a local HaltingState and then halting. Any subsequent messages received by the sate machine will cause the haltedProcessMassage to be called.
If it is desirable to completely stop the state machine call quit or quitNow. This will call the current sate’s exit and its parents, call onQuitting, and then exit Thread / Loopers.
In addition to processMessage, each State has an enter method and an exit method that can be overridden.
Hence the states are sorted in a transition architecture (h for a new state caused by the current states to exit and enter a new state.) For the list of states entered / exited from parent state to find the current state We would then exit the current state and return to its parent state but exclude the parent state and then enter all the new states below the parent normal state goes down to the target state below If there is no parent normal state all states are exited, and then new states entered.
The other two methods that states can use are deferMessage and sendMessageAtFrontOfQueue. sendMessageAtFrontOfQueue sends a mesage but places it in front of the queue instead of behind. deferMessage caused by the message to stay in a list until a transition is made to reach a new state. At the moment all deferred messages will be pushed to the front of the state machine queue with the oldest message in front. This will be processed by the current new state before any other messages that are on the queue or may be added later. Both of these are protected and can only be invoked from the state machine.
To explain some of these properties we will use the state machine with 8 states architecture.
1 2 3 4 5 6 7 8 |
mP0 / mP1 mS0 / mS2 mS1 / mS3 mS4 mS5 ---> initial state |
After mS5 start the list of valid states will be mP0, mP1, mS1 and mS5. So the order of process calls processMessage when a message is received is mS5, mS1, mP1, and mP0 process ensures each processMessage indicates it cannot process this message by returning false or NOT_HANDLE.
Now, to make sure mS5.processMessage receives a message it can handle, and during processing determines the machine should switch the states. It can call transitionTo (mS4) and return either true or HANDLE. Immediately after the process returns from processMessage the state machine runtime will find the normal state of the father, which is mP1. It will then call mS5.exit, mS2.exit, and mS2.enter and then mS4.enter. The list of new valid states are mP0, mP1, mS2, and mS4. So when the next message received mS4.processMessage will be called.
Now for the concrete examples, this is the traditional HelloWorld application as a State Machine. It returns “Hello World” which is printed to the log for each message.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class HelloWorld extends StateMachine { HelloWorld(String name) { super(name); addState(mState1); setInitialState(mState1); } public static HelloWorld makeHelloWorld() { HelloWorld hw = new HelloWorld("hw"); hw.start(); return hw; } class State1 extends State { @Override public boolean processMessage(Message message) { log("Hello World"); return HANDLED; } } State1 mState1 = new State1(); } void testHelloWorld() { HelloWorld hw = makeHelloWorld(); hw.sendMessage(hw.obtainMessage()); } |
One more interest in the state machine is the one with 4 states with two independent parent states.
1 2 3 4 |
mP1 mP2 / mS2 mS1 |
Here is the description of this state machine using pseudo code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
state mP1 { enter { log("mP1.enter"); } exit { log("mP1.exit"); } on msg { CMD_2 { send(CMD_3); defer(msg); transitionTo(mS2); return HANDLED; } return NOT_HANDLED; } } INITIAL state mS1 parent mP1 { enter { log("mS1.enter"); } exit { log("mS1.exit"); } on msg { CMD_1 { transitionTo(mS1); return HANDLED; } return NOT_HANDLED; } } state mS2 parent mP1 { enter { log("mS2.enter"); } exit { log("mS2.exit"); } on msg { CMD_2 { send(CMD_4); return HANDLED; } CMD_3 { defer(msg); transitionTo(mP2); return HANDLED; } return NOT_HANDLED; } } state mP2 { enter { log("mP2.enter"); send(CMD_5); } exit { log("mP2.exit"); } on msg { CMD_3, CMD_4 { return HANDLED; } CMD_5 { transitionTo(HaltingState); return HANDLED; } return NOT_HANDLED; } } |
The implementation is below also in StateMachineTest:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
class Hsm1 extends StateMachine { public static final int CMD_1 = 1; public static final int CMD_2 = 2; public static final int CMD_3 = 3; public static final int CMD_4 = 4; public static final int CMD_5 = 5; public static Hsm1 makeHsm1() { log("makeHsm1 E"); Hsm1 sm = new Hsm1("hsm1"); sm.start(); log("makeHsm1 X"); return sm; } Hsm1(String name) { super(name); log("ctor E"); // Add states, use indentation to show hierarchy addState(mP1); addState(mS1, mP1); addState(mS2, mP1); addState(mP2); // Set the initial state setInitialState(mS1); log("ctor X"); } class P1 extends State { @Override public void enter() { log("mP1.enter"); } @Override public boolean processMessage(Message message) { boolean retVal; log("mP1.processMessage what=" + message.what); switch(message.what) { case CMD_2: // CMD_2 will arrive in mS2 before CMD_3 sendMessage(obtainMessage(CMD_3)); deferMessage(message); transitionTo(mS2); retVal = HANDLED; break; default: // Any message we don't understand in this state invokes unhandledMessage retVal = NOT_HANDLED; break; } return retVal; } @Override public void exit() { log("mP1.exit"); } } class S1 extends State { @Override public void enter() { log("mS1.enter"); } @Override public boolean processMessage(Message message) { log("S1.processMessage what=" + message.what); if (message.what == CMD_1) { // Transition to ourself to show that enter/exit is called transitionTo(mS1); return HANDLED; } else { // Let parent process all other messages return NOT_HANDLED; } } @Override public void exit() { log("mS1.exit"); } } class S2 extends State { @Override public void enter() { log("mS2.enter"); } @Override public boolean processMessage(Message message) { boolean retVal; log("mS2.processMessage what=" + message.what); switch(message.what) { case(CMD_2): sendMessage(obtainMessage(CMD_4)); retVal = HANDLED; break; case(CMD_3): deferMessage(message); transitionTo(mP2); retVal = HANDLED; break; default: retVal = NOT_HANDLED; break; } return retVal; } @Override public void exit() { log("mS2.exit"); } } class P2 extends State { @Override public void enter() { log("mP2.enter"); sendMessage(obtainMessage(CMD_5)); } @Override public boolean processMessage(Message message) { log("P2.processMessage what=" + message.what); switch(message.what) { case(CMD_3): break; case(CMD_4): break; case(CMD_5): transitionToHaltingState(); break; } return HANDLED; } @Override public void exit() { log("mP2.exit"); } } @Override void onHalting() { log("halting"); synchronized (this) { this.notifyAll(); } } P1 mP1 = new P1(); S1 mS1 = new S1(); S2 mS2 = new S2(); P2 mP2 = new P2(); } |
If this is done by sending two messages, CMD_1 and CMD_2 (Note that synchronization is only needed because we use hsm.wait () ).
1 2 3 4 5 6 7 8 9 10 11 12 |
Hsm1 hsm = makeHsm1(); synchronize(hsm) { hsm.sendMessage(obtainMessage(hsm.CMD_1)); hsm.sendMessage(obtainMessage(hsm.CMD_2)); try { // wait for the messages to be handled hsm.wait(); } catch (InterruptedException e) { loge("exception while waiting " + e.getMessage()); } } |
And the result will be as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
D/hsm1 ( 1999): makeHsm1 E D/hsm1 ( 1999): ctor E D/hsm1 ( 1999): ctor X D/hsm1 ( 1999): mP1.enter D/hsm1 ( 1999): mS1.enter D/hsm1 ( 1999): makeHsm1 X D/hsm1 ( 1999): mS1.processMessage what=1 D/hsm1 ( 1999): mS1.exit D/hsm1 ( 1999): mS1.enter D/hsm1 ( 1999): mS1.processMessage what=2 D/hsm1 ( 1999): mP1.processMessage what=2 D/hsm1 ( 1999): mS1.exit D/hsm1 ( 1999): mS2.enter D/hsm1 ( 1999): mS2.processMessage what=2 D/hsm1 ( 1999): mS2.processMessage what=3 D/hsm1 ( 1999): mS2.exit D/hsm1 ( 1999): mP1.exit D/hsm1 ( 1999): mP2.enter D/hsm1 ( 1999): mP2.processMessage what=3 D/hsm1 ( 1999): mP2.processMessage what=4 D/hsm1 ( 1999): mP2.processMessage what=5 D/hsm1 ( 1999): mP2.exit D/hsm1 ( 1999): halting |
State Machine source code in Android SDK: https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/com/android/internal/util/StateMachine.java
P / S
Posts on my viblo, if there is a Source section, then this is a translation from the source that is linked to the original post in this section. These are the articles I select + search + synthesized from Google in the process of handling issues when making real + projects useful and interesting for myself. => Translated as an article to rummage through when necessary. Therefore, when reading the article, everyone should note: