2018-01-08 | learn

android-statemachine-update

http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/java/com/android/internal/util/StateMachine.java


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.

一个状态就是一个 State 对象,必须实现 processMessage 方法,可选择实现 enter/exit/getName 方法。
enter/exit 方法就像面向对象编程中的构造函数和析构函数一样,用于初始化和释放与状态相关的内容。
getName 方法就是返回名字的,默认是类名。你要是在状态机里多次实例化一个 State 时记得把它重写返回实例的名字。

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.

创建一个状态机,我们使用 addState 方法构建层级,setInitialState 方法明确状态机的初始状态。构建完毕后调用 start 方法初始化并启动状态机。
启动后状态机自动执行的第一个动作就是 enter 初始状态。怎么进?其实就是执行从初始状态最上层父状态开始一直到初始状态enter 方法。
这个调用会在状态机内部 Handler 所在线程执行,而不是调用 start 的线程,并在处理后续 Message 前执行完毕(或者说执行完才会去处理消息)。
譬如下边这个例子,先后执行 mP1.enter -> mS1.enter,后续 message 都会交由当前状态处理;在我们的例子里也就是交给初始状态 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.

状态机创建启动后,使用 sendMessage 方法向状态机发送消息,消息使用 obtainMessage 方法创建。
状态机接收到消息后,当前状态的 processMessage 方法就会被调用。
上面的例子 mS1.processMessage 会被第一个执行。
使用 transitionTo 可以切换状态机当前状态。

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.

状态机里的每一个状态最多只能有一个父状态,但是允许没有父状态。如果一个子状态不能 handle 收到的 message,
在 processMessage 方法返回 false 或是 NOT_HANDLED,就可将这个消息交由父状态处理。
如果没有一个状态可以处理这个消息,那就得请 unhandledMessage 方法出面看一看能不能管管,他要是也管不了状态机会直接丢弃这个消息,就当此时无发生过。

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.

当所有东西都处理完毕,状态机可以选择调用 transitionToHaltingState 方法。在当前 processingMessage 返回后,
状态机会转移到一个内部状态 HaltingState,调用 halting 方法。状态机收到的后续消息队列都会交由 haltedProcessMessage 方法处理。

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.

调用 quit or quiteNow 可以完全退出状态机。当前状态到其最上层父状态的 exit 方法会依次执行,
onQuiting 被调用,并完全退出 Thread/Loopers。

In addition to processMessage each State has
an enter method and exit method which may be overridden.

State 除了 processMessage 方法,还有 enterexit 可以被重写。

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.

因为状态是层级的形式,所以转移到新 State 的同时伴随着当前 state 的 exit 和 new state 的 enter
我们通过找到这两个 State 最近的公共父状态,来确定 transition 所需要 enter/exit 的最短路径。
接着对子节点和其父节点(不包含公共父节点)执行 exit,对公共父节点到 new state 之间包括 new state 的状态执行 enter。
如果之间没有公共父节点,那就 exit 到最外层, 再 enter 到 new state。

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.

此外还有两个方法需要介绍, deferMessagesendMessageAtFrontOfQueue
sendMessageAtFrontOfQueue 可以把消息放到队列的最前端,优先处理。
deferMessage 用于把消息保存在一个 list 里,直到下次调用 transition(也就是说当前状态不管这个消息,踢皮球把这个活给下一个上任的状态做)。
之后所有被 defer 的 message 会被放到消息队列的前端,最老的 message 最先被处理。
两个方法都是 protected 的,只能在状态机内部使用。

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.

start 执行完毕后,当前处于 active 的状态包括 mP0, mP1, mS1 and mS5。
初始化状态为 mS5,这时我们假设有一个 mP0, mP1, mS1 and mS5 都不能处理的消息来了,
processMessage 被调用的顺序依次为 mS5, mS1, mP1, mP0。

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.

然后又来了个 mS5 能搞定的消息,这个消息要 transitionTo(mS4),并返回了 true or HANDLED。
状态机就去找公共父状态,也就是 mP1。接下来就是调用 mS5.exit, mS1.exit, mS2.enter and then
mS4.enter。处于 active 的新状态 list 是 mP0, mP1, mS2 and mS4。后续收到的 message 交给
mS4.processMessage 方法处理。


PS 1.关于最短路径

这里用的方法很取巧,状态机中用于记录 State 信息的类是 StateInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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() {
return "state=" + state.getName() + ",active=" + active + ",parent="
+ ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
}
}

关键在于 active,找公共父状态就是用这个东西。
看这个例子:

           mP0
          /   \
         mP1   mS0
        /   \
       mS2   mS1
      /  \    \
     mS3  mS4  mS5  ---> initial state
 

此时 mP0 mP1 mS1 mS5 对应的 active = true

接下来 transitionTo(mS4)

只要从 mS4 开始一直找他的 parent state,
直到一个 active = true 或者 parent = null 就可以确定这个路径了。


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.

一个更具体的例子:

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
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.

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
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:

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
133
134
135
136
137
138
139
140
141
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())

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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