在看藍(lán)牙源碼的時候暖眼,發(fā)現(xiàn)藍(lán)牙的連接狀態(tài)以及綁定狀態(tài)是通過StateMachine(狀態(tài)機(jī))
來實(shí)現(xiàn)的。通過StateMachine
來管理不同狀態(tài)下的行為動作纺裁,提高靈活性诫肠。除了藍(lán)牙,wifi、wps同樣是通過StateMachine
來處理狀態(tài)栋豫。
先舉兩個個例子來看看StateMachine
是如何使用的惭缰,例子是從源碼注釋里頭直接copy來的。在StateMachine
源碼中可以看到它被@Hide
了笼才,所以開發(fā)中漱受,我們是用不了。
- 例1
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 void enter() {
super.enter();
log("State1 enter");
}
@Override
public void exit() {
super.exit();
log("State1 exit");
}
Override
public boolean processMessage(Message message) {
log("Hello World");
return HANDLED;
}
}
State1 mState1 = new State1();
}
// 測試代碼
void testHelloWorld() {
HelloWorld hw = makeHelloWorld();
hw.sendMessage(hw.obtainMessage());
}
執(zhí)行結(jié)果:
State1 enter
Hello World
從這個例子中可以了解到StateMachine
的使用流程分三步走:
1. `add(state)` 添加狀態(tài)骡送,將所有的狀態(tài)都add進(jìn)去;
2. `setInitialState(state)` 設(shè)置初始狀態(tài)
3. `start()` 啟動狀態(tài)機(jī)
從日志上可以看出昂羡,狀態(tài)機(jī)啟動之后,初始狀態(tài)的enter()
方法會被執(zhí)行摔踱,然后往狀態(tài)機(jī)里頭發(fā)送message虐先,初始狀態(tài)的processMessage(msg)
方法會被執(zhí)行。由于沒有切換到其他狀態(tài)派敷,所以exit()
方法未被執(zhí)行蛹批。
- 例2
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();
}
// 測試代碼
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());
}
}
這個例子中有4個狀態(tài),5條命令篮愉,多次的狀態(tài)切換腐芍。
-
step1: 構(gòu)建狀態(tài)機(jī)并添加狀態(tài),添加完?duì)顟B(tài)之后的狀態(tài)樹如下:
mP1 mP2 / \ 初始值->mS1 mS2
其中mP1是mS1和mS2的父狀態(tài)。
-
step2: 調(diào)用
start()
方法试躏,啟動狀態(tài)機(jī)猪勇,此時會打印如下日志:makeHsm1 E ctor E ctor X mP1.enter mS1.enter makeHsm1 X
雖然設(shè)置的初始狀態(tài)是mS1,但是mP1的
enter()
方法也被調(diào)用颠蕴,具體原因會在原理分析中說明泣刹。 -
step3: 發(fā)送
CMD_1
指令,當(dāng)前狀態(tài)是S1犀被,并且S1會處理該指令事件椅您,通過方法transitionTo(mS1)
將狀態(tài)重新切換到S1,此時會打印的日志如下:mS1.processMessage what=1 mS1.exit mS1.enter
調(diào)用
transitionTo()
方法的時候寡键,上一個狀態(tài)會先調(diào)用exit()
方法掀泳,然后新的狀態(tài)調(diào)用enter()
方法:oldState.exit() -> newState.enter()
。 -
step4: 發(fā)送
CMD_2
指令昌腰,此時的日志如下:mS1.processMessage what=2 mP1.processMessage what=2
S1中是不處理
CMD_2
的开伏,但是它的父狀態(tài)會處理,那么就交給P1去處理遭商,具體原因后面分析固灵。 -
step5:P1接收到
CMD_2
指令之后,發(fā)送CMD_3
指令劫流,并且通過deferMessage(CMD_2)
方法將CMD_2
放到消息隊(duì)列的頂端巫玻,然后切換到S2狀態(tài)丛忆,日志如下:mS1.exit mS2.enter mS2.processMessage what=2 mS2.processMessage what=3
上個狀態(tài)是S1,切換到S2仍秤,所以S1
exit()
, S2enter()
,由于S1和S2是父狀態(tài)都是P1熄诡,那么P1維持不變。 -
step6:S2依次接收到
CMD_2
诗力、CMD_3
指令凰浮,先后執(zhí)行發(fā)送CMD_4
指令、defermessage(CMD_3)
苇本、切換到P2狀態(tài)袜茧,日志如下:mS2.exit mP1.exit mP2.enter mP2.processMessage what=3 mP2.processMessage what=4
S2切換到P2,S2先
exit()
瓣窄,然后它父狀態(tài)P1exit()
笛厦,最后P2執(zhí)行enter()
,接著陸續(xù)處理CMD_3
俺夕、CMD_4
事件裳凸。 -
step6:P2在執(zhí)行
enter()
方法的時候,發(fā)送了CMD_5
指令劝贸,CMD_5
指令是停止?fàn)顟B(tài)機(jī)姨谷,那么就將執(zhí)行P2的exit()
。日志如下:mP2.exit halting
StateMachine
使用總結(jié)
- 在狀態(tài)機(jī)的構(gòu)造方法里頭悬荣,通過
addState()
方法構(gòu)建狀態(tài)樹菠秒; - 通過
setInitialState()
設(shè)置初始狀態(tài)疙剑; - 通過
start()
方法啟動狀態(tài)機(jī)氯迂,初始狀態(tài)執(zhí)行enter()
方法; -
sendMessage(msg)
給狀態(tài)機(jī)發(fā)送消息之后言缤,當(dāng)前狀態(tài)會執(zhí)行processMessage(msg)
嚼蚀,來處理消息,如果當(dāng)前狀態(tài)處理不了管挟,則交給父狀態(tài)處理轿曙,如果都處理不了,交給狀態(tài)機(jī)處理僻孝; - 通過
transitionTo(state)
來切換狀態(tài)导帝,oldState執(zhí)行exit()
,newState執(zhí)行enter()
穿铆; -
deferMessage(msg)
暫時將當(dāng)前消息保存到消息隊(duì)列的頂端您单, 一旦切換到新的狀態(tài),首先處理該消息荞雏; - 切換到某個狀態(tài)的時候虐秦,先會從當(dāng)前狀態(tài)開始往根狀態(tài)依次執(zhí)行各狀態(tài)的
exit()
方法平酿,直到與新狀態(tài)相同的父狀態(tài)Px為止。然后依次執(zhí)行從Px到新狀態(tài)路徑下各狀態(tài)的enter()
方法(需要注意的是悦陋,公共父狀態(tài)的enter和exit方法不會執(zhí)行)蜈彼。