public class StateMachine extends Object
The state machine defined here is a hierarchical state machine which processes messages and can have states arranged hierarchically.
A state is a State
object and must implement
processMessage
and optionally enter/exit/getName
.
The enter/exit methods are equivalent to the construction and destruction
in Object Oriented programming and are used to perform initialization and
cleanup of the state respectively. The getName
method returns the
name of the state; the default implementation returns the class name. It may be
desirable to have getName
return the the state instance name instead,
in particular if a particular state class has multiple instances.
When a state machine is created, addState
is used to build the
hierarchy and setInitialState
is used to identify which of these
is the initial state. After construction the programmer calls start
which initializes and starts the state machine. The first action the StateMachine
is to the invoke enter
for all of the initial state's hierarchy,
starting at its eldest parent. The calls to enter will be done in the context
of the StateMachine's Handler, not in the context of the call to start, and they
will be invoked before any messages are processed. For example, given the simple
state machine below, mP1.enter will be invoked 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 would initially be mS1.processMessage.
mP1 / \ mS2 mS1 ----> initial state
After the state machine is created and started, messages are sent to a state
machine using sendMessage
and the messages are created using
obtainMessage
. When the state machine receives a message the
current state's processMessage
is invoked. In the above example
mS1.processMessage will be invoked first. The state may use transitionTo
to change the current state to a new state.
Each state in the state machine may have a zero or one parent states. If
a child state is unable to handle a message it may have the message processed
by its parent by returning false or NOT_HANDLED. If a message is not handled by
a child state or any of its ancestors, unhandledMessage
will be invoked
to give one last chance for the state machine to process the message.
When all processing is completed a state machine may choose to call
transitionToHaltingState
. When the current processingMessage
returns the state machine will transfer to an internal HaltingState
and invoke halting
. Any message subsequently received by the state
machine will cause haltedProcessMessage
to be invoked.
If it is desirable to completely stop the state machine call quit
or
quitNow
. These will call exit
of the current state and its parents,
call onQuitting
and then exit Thread/Loopers.
In addition to processMessage
each State
has
an enter
method and exit
method which may be overridden.
Since the states are arranged in a hierarchy transitioning to a new state causes current states to be exited and new states to be entered. To determine the list of states to be entered/exited the common parent closest to the current state is found. We then exit from the current state and its parent's up to but not including the common parent state and then enter all of the new states below the common parent down to the destination state. If there is no common parent all states are exited and then the new states are entered.
Two other methods that states can use are deferMessage
and
sendMessageAtFrontOfQueue
. The sendMessageAtFrontOfQueue
sends
a message but places it on the front of the queue rather than the back. The
deferMessage
causes the message to be saved on a list until a
transition is made to a new state. At which time all of the deferred messages
will be put on the front of the state machine queue with the oldest message
at the front. These will then be processed by the new current state before
any other messages that are on the queue or might be added later. Both of
these are protected and may only be invoked from within a state machine.
To illustrate some of these properties we'll use state machine with an 8 state hierarchy:
mP0 / \ mP1 mS0 / \ mS2 mS1 / \ \ mS3 mS4 mS5 ---> initial state
After starting mS5 the list of active states is mP0, mP1, mS1 and mS5. So the order of calling processMessage when a message is received is mS5, mS1, mP1, mP0 assuming each processMessage indicates it can't handle this message by returning false or NOT_HANDLED.
Now assume mS5.processMessage receives a message it can handle, and during the handling determines the machine should change states. It could call transitionTo(mS4) and return true or HANDLED. Immediately after returning from processMessage the state machine runtime will find the common parent, which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So when the next message is received mS4.processMessage will be invoked.
Now for some concrete examples, here is the canonical HelloWorld as a state machine. It responds with "Hello World" being printed to the log for every message.
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()); }
A more interesting state machine is one with four states with two independent parent states.
mP1 mP2 / \ mS2 mS1
Here is a description of this state machine using pseudo code.
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 and also in StateMachineTest:
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 executed by sending two messages CMD_1 and CMD_2 (Note the synchronize is only needed because we use hsm.wait())
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()); } }
The output is:
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
Modifier and Type | Class and Description |
---|---|
static class |
StateMachine.LogRec
StateMachine logging record.
|
Modifier and Type | Field and Description |
---|---|
static boolean |
HANDLED
Convenience constant that maybe returned by processMessage
to indicate the the message was processed and is not to be
processed by parent states
|
static boolean |
NOT_HANDLED
Convenience constant that maybe returned by processMessage
to indicate the the message was NOT processed and is to be
processed by parent states
|
Modifier | Constructor and Description |
---|---|
protected |
StateMachine(String name)
Constructor creates a StateMachine with its own thread.
|
protected |
StateMachine(String name,
Handler handler)
Constructor creates a StateMachine using the handler.
|
protected |
StateMachine(String name,
Looper looper)
Constructor creates a StateMachine using the looper.
|
Modifier and Type | Method and Description |
---|---|
protected void |
addLogRec(String string)
Add the string to LogRecords.
|
protected void |
addState(State state)
Add a new state to the state machine, parent will be null
|
protected void |
addState(State state,
State parent)
Add a new state to the state machine
|
Collection<StateMachine.LogRec> |
copyLogRecs() |
protected void |
deferMessage(Message msg)
Defer this message until next state transition.
|
void |
dump(FileDescriptor fd,
PrintWriter pw,
String[] args)
Dump the current state.
|
protected Message |
getCurrentMessage() |
protected IState |
getCurrentState() |
Handler |
getHandler() |
StateMachine.LogRec |
getLogRec(int index) |
int |
getLogRecCount() |
int |
getLogRecSize() |
protected String |
getLogRecString(Message msg)
Return a string to be logged by LogRec, default
is an empty string.
|
String |
getName() |
protected String |
getWhatToString(int what) |
protected void |
haltedProcessMessage(Message msg)
Called for any message that is received after
transitionToHalting is called.
|
protected boolean |
hasDeferredMessages(int what)
Check if there are any pending messages with code 'what' in deferred messages queue.
|
protected boolean |
hasMessages(int what)
Check if there are any pending posts of messages with code 'what' in
the message queue.
|
boolean |
isDbg() |
protected boolean |
isQuit(Message msg)
|
protected void |
log(String s)
Log with debug
|
protected void |
logAndAddLogRec(String s)
Log with debug and add to the LogRecords.
|
protected void |
logd(String s)
Log with debug attribute
|
protected void |
loge(String s)
Log with error attribute
|
protected void |
loge(String s,
Throwable e)
Log with error attribute
|
protected void |
logi(String s)
Log with info attribute
|
protected void |
logv(String s)
Log with verbose attribute
|
protected void |
logw(String s)
Log with warning attribute
|
Message |
obtainMessage()
Get a message and set Message.target state machine handler.
|
Message |
obtainMessage(int what)
Get a message and set Message.target state machine handler, what.
|
Message |
obtainMessage(int what,
int arg1)
Get a message and set Message.target state machine handler,
what, arg1 and arg2
Note: The handler can be null if the state machine has quit,
which means target will be null and may cause a AndroidRuntimeException
in MessageQueue#enqueMessage if sent directly or if sent using
StateMachine#sendMessage the message will just be ignored.
|
Message |
obtainMessage(int what,
int arg1,
int arg2)
Get a message and set Message.target state machine handler,
what, arg1 and arg2
Note: The handler can be null if the state machine has quit,
which means target will be null and may cause a AndroidRuntimeException
in MessageQueue#enqueMessage if sent directly or if sent using
StateMachine#sendMessage the message will just be ignored.
|
Message |
obtainMessage(int what,
int arg1,
int arg2,
Object obj)
Get a message and set Message.target state machine handler,
what, arg1, arg2 and obj
Note: The handler can be null if the state machine has quit,
which means target will be null and may cause a AndroidRuntimeException
in MessageQueue#enqueMessage if sent directly or if sent using
StateMachine#sendMessage the message will just be ignored.
|
Message |
obtainMessage(int what,
Object obj)
Get a message and set Message.target state machine handler,
what and obj.
|
protected void |
onHalting()
This will be called once after handling a message that called
transitionToHalting.
|
protected void |
onPostHandleMessage(Message msg)
Notifies subclass that the StateMachine handler has finished processing the Message msg and
has possibly transitioned to a new state.
|
protected void |
onPreHandleMessage(Message msg)
Notifies subclass that the StateMachine handler is about to process the Message msg
|
protected void |
onQuitting()
This will be called once after a quit message that was NOT handled by
the derived StateMachine.
|
protected void |
quit()
Quit the state machine after all currently queued up messages are processed.
|
protected void |
quitNow()
Quit the state machine immediately all currently queued messages will be discarded.
|
protected boolean |
recordLogRec(Message msg) |
protected void |
removeDeferredMessages(int what)
Removes a message from the deferred messages queue.
|
protected void |
removeMessages(int what)
Removes a message from the message queue.
|
void |
sendMessage(int what)
Enqueue a message to this state machine.
|
void |
sendMessage(int what,
int arg1)
Enqueue a message to this state machine.
|
void |
sendMessage(int what,
int arg1,
int arg2)
Enqueue a message to this state machine.
|
void |
sendMessage(int what,
int arg1,
int arg2,
Object obj)
Enqueue a message to this state machine.
|
void |
sendMessage(int what,
Object obj)
Enqueue a message to this state machine.
|
void |
sendMessage(Message msg)
Enqueue a message to this state machine.
|
protected void |
sendMessageAtFrontOfQueue(int what)
Enqueue a message to the front of the queue for this state machine.
|
protected void |
sendMessageAtFrontOfQueue(int what,
int arg1)
Enqueue a message to the front of the queue for this state machine.
|
protected void |
sendMessageAtFrontOfQueue(int what,
int arg1,
int arg2)
Enqueue a message to the front of the queue for this state machine.
|
protected void |
sendMessageAtFrontOfQueue(int what,
int arg1,
int arg2,
Object obj)
Enqueue a message to the front of the queue for this state machine.
|
protected void |
sendMessageAtFrontOfQueue(int what,
Object obj)
Enqueue a message to the front of the queue for this state machine.
|
protected void |
sendMessageAtFrontOfQueue(Message msg)
Enqueue a message to the front of the queue for this state machine.
|
void |
sendMessageDelayed(int what,
int arg1,
int arg2,
long delayMillis)
Enqueue a message to this state machine after a delay.
|
void |
sendMessageDelayed(int what,
int arg1,
int arg2,
Object obj,
long delayMillis)
Enqueue a message to this state machine after a delay.
|
void |
sendMessageDelayed(int what,
int arg1,
long delayMillis)
Enqueue a message to this state machine after a delay.
|
void |
sendMessageDelayed(int what,
long delayMillis)
Enqueue a message to this state machine after a delay.
|
void |
sendMessageDelayed(int what,
Object obj,
long delayMillis)
Enqueue a message to this state machine after a delay.
|
void |
sendMessageDelayed(Message msg,
long delayMillis)
Enqueue a message to this state machine after a delay.
|
void |
setDbg(boolean dbg)
Set debug enable/disabled.
|
protected void |
setInitialState(State initialState)
Set the initial state.
|
void |
setLogOnlyTransitions(boolean enable)
Set to log only messages that cause a state transition
|
void |
setLogRecSize(int maxSize)
Set number of log records to maintain and clears all current records.
|
void |
start()
Start the state machine.
|
String |
toString()
Returns a string representation of the object.
|
protected void |
transitionTo(IState destState)
transition to destination state.
|
protected void |
transitionToHaltingState()
transition to halt state.
|
protected void |
unhandledMessage(Message msg)
Called when message wasn't handled
|
public static final boolean HANDLED
public static final boolean NOT_HANDLED
protected StateMachine(String name)
name
- of the state machineprotected StateMachine(String name, Looper looper)
name
- of the state machineprotected void onPreHandleMessage(Message msg)
msg
- The message that is being handledprotected void onPostHandleMessage(Message msg)
msg
- The message that is being handledprotected final void addState(State state, State parent)
state
- the state to addparent
- the parent of stateprotected final void addState(State state)
state
- to addprotected final void setInitialState(State initialState)
initialState
- is the state which will receive the first message.protected final Message getCurrentMessage()
protected final IState getCurrentState()
protected final void transitionTo(IState destState)
destState
- will be the state that receives the next message.protected final void transitionToHaltingState()
protected final void deferMessage(Message msg)
msg
- is deferred until the next transition.protected void unhandledMessage(Message msg)
msg
- that couldn't be handled.protected void haltedProcessMessage(Message msg)
protected void onHalting()
haltedProcessMessage(Message)
protected void onQuitting()
public final String getName()
public final void setLogRecSize(int maxSize)
maxSize
- number of messages to maintain at anyone time.public final void setLogOnlyTransitions(boolean enable)
enable
- true
to enable, false
to disablepublic final int getLogRecSize()
public final int getLogRecCount()
public final StateMachine.LogRec getLogRec(int index)
public final Collection<StateMachine.LogRec> copyLogRecs()
protected void addLogRec(String string)
string
- protected boolean recordLogRec(Message msg)
protected String getLogRecString(Message msg)
msg
- that was processedprotected String getWhatToString(int what)
public final Handler getHandler()
public final Message obtainMessage()
public final Message obtainMessage(int what)
what
- is the assigned to Message.what.public final Message obtainMessage(int what, Object obj)
what
- is the assigned to Message.what.obj
- is assigned to Message.obj.public final Message obtainMessage(int what, int arg1)
what
- is assigned to Message.whatarg1
- is assigned to Message.arg1public final Message obtainMessage(int what, int arg1, int arg2)
what
- is assigned to Message.whatarg1
- is assigned to Message.arg1arg2
- is assigned to Message.arg2public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
what
- is assigned to Message.whatarg1
- is assigned to Message.arg1arg2
- is assigned to Message.arg2obj
- is assigned to Message.objpublic void sendMessage(int what)
public void sendMessage(int what, Object obj)
public void sendMessage(int what, int arg1)
public void sendMessage(int what, int arg1, int arg2)
public void sendMessage(int what, int arg1, int arg2, Object obj)
public void sendMessage(Message msg)
public void sendMessageDelayed(int what, long delayMillis)
public void sendMessageDelayed(int what, Object obj, long delayMillis)
public void sendMessageDelayed(int what, int arg1, long delayMillis)
public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis)
public void sendMessageDelayed(int what, int arg1, int arg2, Object obj, long delayMillis)
public void sendMessageDelayed(Message msg, long delayMillis)
protected final void sendMessageAtFrontOfQueue(int what)
protected final void sendMessageAtFrontOfQueue(int what, Object obj)
protected final void sendMessageAtFrontOfQueue(int what, int arg1)
protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2)
protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj)
protected final void sendMessageAtFrontOfQueue(Message msg)
protected final void removeMessages(int what)
protected final void removeDeferredMessages(int what)
protected final boolean hasDeferredMessages(int what)
protected final boolean hasMessages(int what)
protected final boolean isQuit(Message msg)
protected final void quit()
protected final void quitNow()
public boolean isDbg()
public void setDbg(boolean dbg)
dbg
- is true to enable debugging.public void start()
public void dump(FileDescriptor fd, PrintWriter pw, String[] args)
fd
- pw
- args
- public String toString()
Object
toString
method returns a string that
"textually represents" this object. The result should
be a concise but informative representation that is easy for a
person to read.
It is recommended that all subclasses override this method.
The toString
method for class Object
returns a string consisting of the name of the class of which the
object is an instance, the at-sign character `@
', and
the unsigned hexadecimal representation of the hash code of the
object. In other words, this method returns a string equal to the
value of:
getClass().getName() + '@' + Integer.toHexString(hashCode())
protected void logAndAddLogRec(String s)
s
- is string logprotected void log(String s)
s
- is string logprotected void logd(String s)
s
- is string logprotected void logv(String s)
s
- is string logprotected void logi(String s)
s
- is string logprotected void logw(String s)
s
- is string logprotected void loge(String s)
s
- is string log