案例:咱們常見的汽車,咱們可使用它行駛,也能夠將它中止在路邊。當它在行駛的過程當中,須要不斷的檢測油量,一旦油量不足的時候,就將陷入中止狀態。而中止在路邊的汽車,須要點火啓動,此時將檢測車中的油量,當油量不足的時候,汽車就須要去加油站加油。android
當咱們對汽車的狀態和行爲進行抽象,汽車的狀態能夠有 :api
而咱們能夠對汽車的操做能夠是:數組
咱們創建一個二維表,將狀態和可操做的行爲組合在一塊兒:緩存
咱們經過這個狀態表構建咱們的狀態引用關係模型:安全
這幅狀態圖實際上一個相對複雜的網狀圖形,當構建一個更爲複雜的系統的時候,這種網狀圖將會以成倍的複雜性遞增。爲了解決這個問題,咱們須要將這種網狀的狀態機轉化爲一個樹狀的層次狀態機,也叫 HSM (Hierarchical State Machine)。咱們能夠將上述的狀態模型轉化爲:bash
這張圖裏,將 STOP
做爲根節點,從層次上做爲其餘狀態節點的父節點。數據結構
STOP
做爲初始狀態ACTION_ADD_OIL
動做,STOP
狀態就變成了 ADDING_OIL
狀態ADDING_OIL
結束,發生了 ACTION_RUN
動做,就須要彈出 ADDING_OIL
狀態 ,傳入到 CHECK_OIL
,而後傳入 RUN
狀態。StateMachine
是 Android
系統提供的 HSM
狀態機的實現,它的源碼在包com.android.internal.util
下。StateMachine
提供了三個構造方法,但這三個方法大同小異:async
protected StateMachine(String name) {
mSmThread = new HandlerThread(name);
mSmThread.start();
Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
複製代碼
構造器調用 initStateMachine
函數,這個函數須要傳入了一個 Looper
對象,StateMachine
對象全部的操做都須要在這個 Looper
所在的線程中運行。而之間的通信是經過 SmHandler
對象傳遞。ide
private void initStateMachine(String name, Looper looper) {
mName = name;
mSmHandler = new SmHandler(looper, this);
}
複製代碼
上面咱們說了,StateMachine
是 HSM
狀態機,構造它的時候,須要指定它的層次關係,這須要調用 addState
函數,這個函數有兩個參數,第第二個參數表明的是第一個參數的父節點:函數
protected final void addState(State state, State parent) {
mSmHandler.addState(state, parent);
}
複製代碼
而根節點,又稱爲初始狀態節點,須要經過 setInitialState
函數指定:
protected final void setInitialState(State initialState) {
mSmHandler.setInitialState(initialState);
}
複製代碼
這裏,不論設置什麼樣的節點,都須要經過 mSmHandler
對象設置,好比,當經過調用 StateMachine.addState
添加節點的時候,須要調用到 SmHandler.addState
函數:
//code SmHandler
private final StateInfo addState(State state, State parent) {
if (mDbg) {
//debug開關能夠經過 StateMachine.setDbg接口設置打開
mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
+ ((parent == null) ? "" : parent.getName()));
}
StateInfo parentStateInfo = null;
// StateInfo 表示在 HSM 樹中的狀態節點
if (parent != null) {
parentStateInfo = mStateInfo.get(parent);
//mStateInfo 是一個hashmap對象
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) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
return stateInfo;
}
複製代碼
mStateInfo
是一個 HashMap<State, StateInfo>
類型的對象, 而 StateInfo
類是用於記錄狀態 State
對象信息,和父節點信息的 HSM
節點對象
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;
}
複製代碼
按照咱們剛纔對 Car
這個模型的抽象,咱們能夠定義出一個 Car
的狀態機:
public class Car extends StateMachine {
....
public Car(String name) {
super(name);
this.addState(mStopState,null);
//mStopState 做爲根節點狀態,沒有父節點
this.addState(mAddOilState,mStopState);
//mAddOilState 做爲mStopState 的子狀態
this.addState(mCheckOilState,mStopState);
//mCheckOilState 做爲mStopState 的子狀態
this.addState(mRunState,mCheckOilState);
//mRunState 做爲mCheckOilState 的子狀態
this.setInitialState(mStopState);
// mStopState 爲初始狀態
}
}
複製代碼
當咱們構造完咱們的樹形結構了之後,咱們就能夠將咱們的狀態機啓動起來,這個啓動依賴於 StateMachine.start
函數:
public void start() {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
/** Send the complete construction message */
smh.completeConstruction();//調用SmHandler.completeConstruction
}
複製代碼
StateMachine.start
中調用 SmHandler.completeConstruction
用於提交咱們以前的全部操做:
private final void completeConstruction() {
if (mDbg) mSm.log("completeConstruction: E");
/**
* Determine the maximum depth of the state hierarchy
* so we can allocate the state stacks.
*/
int maxDepth = 0;// step1
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) mSm.log("completeConstruction: maxDepth=" + maxDepth);
mStateStack = new StateInfo[maxDepth];
mTempStateStack = new StateInfo[maxDepth];//用於計算的臨時變量
setupInitialStateStack();//以初始狀態爲棧底保存到 mStateStack
/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
if (mDbg) mSm.log("completeConstruction: X");
}
複製代碼
按照咱們的 HSM
模型,以 STOP
狀態爲基礎狀態的時候,那麼咱們以這個狀態爲棧底向上延伸,咱們能夠獲得兩個棧,分別是:
stack1: [STOP,CHECK_OIL,RUN]
stack2: [STOP,ADD_OIL]
複製代碼
stack1
的最大深度爲 3 , stack2
的最大深度爲 2 。那麼 stack1
就能夠應用於 stack2
的狀況。 completeConstruction
代碼中 step1
段的代碼就是這個目的,找到一個最大的棧,用於給全部的棧狀況使用。
private final void setupInitialStateStack() {
if (mDbg) {
mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
}
StateInfo curStateInfo = mStateInfo.get(mInitialState);
for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
mTempStateStack[mTempStateStackCount] = curStateInfo;
curStateInfo = curStateInfo.parentStateInfo;
}
//將初始狀態位根狀態以0->N的順序存入 tempStack
// Empty the StateStack
mStateStackTopIndex = -1;
moveTempStateStackToStateStack();//將 tempstack 倒敘複製給 stateStack
}
複製代碼
mTempStateStack
是一箇中間變量,它存的是倒敘的 mStateStack
。好比咱們的初始狀態是 RUN
。那麼咱們須要不斷循環將 RUN
的父節點存入 mTempStateStack
獲得:
mTempStateStack :[RUN,CHECK_OIL,STOP]
複製代碼
這時候咱們須要調用 moveTempStateStackToStateStack
函數將它倒敘複製到 mStateStack
對象中,保證當前狀態 RUN
位於棧頂:
mStateStack: [STOP,CHECK_OIL,RUN]
複製代碼
mStateStackTopIndex
變量指向 mStateStack
的棧頂。剛纔的這個例子,mStateStackTopIndex
的值爲 2 ,指向 RUN
所在的數組索引位置。
到了 start
函數調用的這一步,咱們就完成了一個樹形數據結構和初始狀態的設置,接下來,咱們就能夠往咱們的狀態機上發送咱們的指令。
咱們經過上面的手段構造完一個狀態機之後,就能夠經過指令讓這個狀態機去處理消息了。咱們先給咱們的狀態機開一些外部調用的接口:
public interface ICar {
public void run();
public void stop();
public void addOil();
}
public class Car extends StateMachine implements ICar{
....
}
public void func() {
ICar car = new Car("Ford");
car.addOil();
car.run();
car.stop();
}
複製代碼
當咱們要向咱們的狀態機發送指令的時候,須要調用狀態機的 sendMessage(...)
函數,這套函數跟 android.os.Handler
提供的 api
的含義如出一轍。實際上,狀態機在處理這種消息的時候,也是採用 Handler
的方式,而咱們上面反覆提到的 SmHandler
對象實際上就是 Handler
對象的子類。
public final void sendMessage(int what) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
smh.sendMessage(obtainMessage(what));//經過Handler方式發送消息
}
複製代碼
這樣,咱們就能夠經過這個函數去實現咱們的幾個接口方法:
public class Car extends StateMachine implements ICar{
...
public void run() {
this.sendMessage(ACTION_RUN);
}
public void stop() {
this.sendMessage(ACTION_STOP);
}
public void addOil() {
this.sendMessage(ACTION_ADD_OIL);
}
}
複製代碼
根據咱們對 Handler
類的瞭解,每當咱們經過 Handler.sendMessage
函數發送一個消息的時候,都將在 Looper
的下個處理消息執行的時候,回調 Handler.handleMessage(Message msg)
方法。因爲 SmHandler
繼承於 Handler
,而且它複寫了 handleMessage
函數,所以 , 消息發送以後,最後將回調到SmHandler.handleMessage
方法中。
//code SmHandler
public final void handleMessage(Message msg) {
if (!mHasQuit) {
if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
/** Save the current message */
mMsg = msg;
/** State that processed the message */
State msgProcessedState = null;
if (mIsConstructionCompleted) {
/** Normal path */
msgProcessedState = processMsg(msg);
//由當前狀態處理
} else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
&& (mMsg.obj == mSmHandlerObj)) {
/** Initial one time path. */
//執行初始化操做函數
mIsConstructionCompleted = true;
invokeEnterMethods(0);
//當調用
} else {
throw new RuntimeException("StateMachine.handleMessage: "
+ "The start method not called, received msg: " + msg);
}
performTransitions(msgProcessedState, msg);
// We need to check if mSm == null here as we could be quitting.
if (mDbg && mSm != null) mSm.log("handleMessage: X");
}
}
複製代碼
SmHandler.handleMessage
函數主要執行如下幾個操做:
mHasQuit
判斷是否退出,若是退出將不執行後續指令mIsConstructionCompleted
),若是初始化完成調用 processMsg
將消息拋給當前狀態執行SM_INIT_CMD
將執行一次初始化操做performTransitions
函數用於轉變當前狀態和 mStateStack
咱們先接着上面第三個主題 [StateMachine] 初始化
看下第三步。 SM_INIT_CMD
指令的發出位於 SmHandler.completeConstruction
函數中:
//code SmHandler
private final void completeConstruction() {
...
/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
...
}
複製代碼
處理初始化消息的時候會先將 mIsConstructionCompleted
設置爲 true
,告訴狀態機已經初始化過了,可讓狀態處理消息了。而後調用了個 invokeEnterMethods
函數。這個函數的目的是回調當前 mStateStack
棧中全部的活動狀態的 enter
方法。而且將非活躍狀態設置爲活躍態:
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
mStateStack[i].state.enter();
mStateStack[i].active = true;
}
}
複製代碼
這樣,若是咱們的初始狀態是 STOP
的話,咱們就能夠在後臺打印中看到:
//console output:
output: [StateMachine] StopState enter
複製代碼
若是咱們的初始狀態是 RUN
狀態的話就能夠看到:
//console output:
output: [StateMachine] StopState enter
output: [StateMachine] CheckOilState enter
output: [StateMachine] RunState enter
複製代碼
上面就是處理初始化消息的過程,到這一步,初始化的過程算是完整走完。咱們繼續來看初始化後的邏輯,當初始化已經結束以後,再收到的消息將經過 processMsg
函數提交給合適的狀態執行。
private final State processMsg(Message msg) {
StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
//獲取當前狀態節點
if (isQuit(msg)) {
//判斷當前消息是不是退出消息
transitionTo(mQuittingState);
} else {
while (!curStateInfo.state.processMessage(msg)) {
//當該狀態不處理當前消息的時候,將委託給父狀態處理
curStateInfo = curStateInfo.parentStateInfo;
if (curStateInfo == null) {
mSm.unhandledMessage(msg);
//當沒有狀態能夠處理當前消息的時候回調unhandledMessage
break;
}
}
}
return (curStateInfo != null) ? curStateInfo.state : null;
}
複製代碼
processMsg
會先判斷當前是不是退出消息,若是 isQuit
成立,將轉入 mQuittingState
狀態。咱們將在後面分析如何執行退出操做,這塊東西,咱們暫且有個印象。當並不是退出消息時候,將會分配給當前狀態處理,若是當前狀態處理不了,將委託給父狀態處理。好比當前咱們的初始狀態是 RUN
。那麼對應的 mStateStack
爲:
[STOP,CHECK_OIL,RUN]
複製代碼
咱們給狀態的測試代碼是:
private class BaseState extends State {
@Override
public void enter() {
log(" enter "+this.getClass().getSimpleName());
super.enter();
}
@Override
public void exit() {
log(" exit "+this.getClass().getSimpleName());
super.exit();
}
}
public class StopState extends BaseState {
@Override
public boolean processMessage(Message msg) {
log("StopState.processMessage");
return HANDLED;//處理消息
}
}
public class CheckOilState extends BaseState {
@Override
public boolean processMessage(Message msg) {
log("CheckOilState.processMessage");
return NOT_HANDLED;// 不處理消息
}
}
public class RunState extends BaseState {}
複製代碼
咱們往狀態機 Car
發送一條消息:
Car car = new Car();
car.sendMessage(0x01);
複製代碼
咱們將在後臺打印出log:
--> enter StopState
--> enter CheckOilState
--> enter RunState
// 初始化結束
-->[StateMachine]:handleMessage 1
-->CheckOilState.processMessage // run狀態不處理,扔給checkoil狀態
-->StopState.processMessage // checkoil狀態不處理,扔給stop 狀態
複製代碼
固然,若是你並不但願消息被委託調用,你能夠在初始狀態調用 processMessage
函數的時候,返回 HANDLED
常量,這樣就不會往下調用。
一般,咱們會在 State.processMessage
內部,經過調用 transitionTo
函數執行一次狀態轉換,而調用這個函數只是將你要轉換的狀態存入一個臨時的對象中:
protected final void transitionTo(IState destState) {
mSmHandler.transitionTo(destState);
}
private final void transitionTo(IState destState) {
mDestState = (State) destState;
}
複製代碼
真正的狀態轉換將發生在 SmHandler.handleMessage
函數執行以後:
public final void handleMessage(Message msg) {
if (!mHasQuit) {
...
performTransitions(msgProcessedState, msg);//變動狀態
}
}
複製代碼
這裏將調用 performTransitions
函數完成狀態轉換,假如,如今的狀態是 RUN
狀態,當須要轉成 ADD_OIL
狀態的時候,將進行一下轉變:
/**
初始:
mStateStack : [ STOP,CHECK_OIL,RUN]
*/
private void performTransitions(State msgProcessedState, Message msg) {
State orgState = mStateStack[mStateStackTopIndex].state;
//orgState記錄當前狀態
State destState = mDestState;
//destState 記錄要轉變的目標狀態
if (destState != null) {
while (true) {
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
//查找跟目標狀態的公共節點狀態,此時爲 STOP 狀態節點
invokeExitMethods(commonStateInfo);
//從棧頂一直到commonStateInfo(不包含) 所在的位置執行退出操做
int stateStackEnteringIndex = moveTempStateStackToStateStack();
invokeEnterMethods(stateStackEnteringIndex);
moveDeferredMessageAtFrontOfQueue();
//將Deferred 消息放入隊列頭部優先執行
if (destState != mDestState) {
destState = mDestState;
} else {
break;
}
}
mDestState = null;
}
if (destState != null) {
if (destState == mQuittingState) {
//TODO clean
} else if (destState == mHaltingState) {
//TODO halt
}
}
}
複製代碼
這段代碼執行的時候,會先去尋找目標節點和當前節點的公共祖先節點,這是經過調用 setupTempStateStackWithStatesToEnter
調用的。StateMachine
的函數名起的見名知意,*Temp*
表明這個函數中要使用中間變量 mTempStateStack
。*ToEnter
表明須要對添加進的狀態執行 State.enter
操做。
private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
mTempStateStackCount = 0;//重置 mTempStateStack
StateInfo curStateInfo = mStateInfo.get(destState);
do {
mTempStateStack[mTempStateStackCount++] = curStateInfo;
curStateInfo = curStateInfo.parentStateInfo;
} while ((curStateInfo != null) && !curStateInfo.active);
//找到第一個 active 的狀態節點。
return curStateInfo;
}
複製代碼
setupTempStateStackWithStatesToEnter
函數就是將目標節點的堆棧複製到 mTempStateStack
變量中,而後將最終相交的節點返回。這裏採用 do-while
的寫法,說明這個函數的執行,至少包含一個 destState
元素。剛纔從 RUN->ADD_OIL
的例子中,setupTempStateStackWithStatesToEnter
將返回 STOP
狀態,mTempStateStack
的爲:
mTempStateStack: {ADD_OIL}
複製代碼
咱們回到 performTransitions
的流程,執行 setupTempStateStackWithStatesToEnter
完,將執行 invokeExitMethods
函數。
private final void invokeExitMethods(StateInfo commonStateInfo) {
while ((mStateStackTopIndex >= 0)
&& (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
State curState = mStateStack[mStateStackTopIndex].state;
curState.exit();
mStateStack[mStateStackTopIndex].active = false;
mStateStackTopIndex -= 1;
}
}
複製代碼
這個函數至關於將 mStateStack
棧中的非 commonStateInfo
進行出棧。
mStateStack: {STOP,CHECK_OIL,RUN} ->
invokeExitMethods(STOP) ->
mStateStack: {STOP}
複製代碼
執行完出棧後,只須要將咱們剛纔構建的 mTempStateStack
拷貝到 mStateStack
就能夠構建新的狀態棧了,而這個操做是經過 moveTempStateStackToStateStack
函數完成,而 moveTempStateStackToStateStack
咱們剛纔說過,實際上就是將 mTempStateStack
逆序賦值到 mStateStack
。這樣,咱們就構建了一個新的 mStateStack
:
mStateStack: {STOP,ADD_OIL}
複製代碼
這個時候,咱們構建了一個新的狀態棧,至關於已經切換了狀態。performTransitions
在執行完 moveTempStateStackToStateStack
以後,調用 invokeEnterMethods
函數,執行非 active
狀態的 enter
方法。以後執行 moveDeferredMessageAtFrontOfQueue
將經過 deferMessage
函數緩存的消息隊列放到 Handler
消息隊列的頭部:
...
int stateStackEnteringIndex = moveTempStateStackToStateStack();
invokeEnterMethods(stateStackEnteringIndex);
moveDeferredMessageAtFrontOfQueue();
//將Deferred 消息放入隊列頭部優先執行
if (destState != mDestState) {
destState = mDestState;
} else {
break;
}
...
複製代碼
當咱們完成狀態的轉換了之後,須要對兩種特殊的狀態進行處理,在 performTransitions
函數的末尾會判斷兩個特殊的狀態:
1. HaltingState
2. QuittingState
複製代碼
狀態機的退出,StateMachine
提供了幾個方法:
從上面的表述中看,quit
相對 halt
操做來講更加的安全。這個 Thread
的 intercept
和 stop
方法很相似,很好理解。上面咱們說到,退出狀態 HaltingState
和 QuittingState
是在performTransitions
函數的末尾判斷和執行的,咱們來看下代碼:
if (destState != null) {
if (destState == mQuittingState) {
mSm.onQuitting();
cleanupAfterQuitting();//清理操做
} else if (destState == mHaltingState) {
mSm.onHalting();//只是執行回調
}
}
private final void cleanupAfterQuitting() {
if (mSm.mSmThread != null) {
getLooper().quit();//退出線程
mSm.mSmThread = null;
}
/*清空數據*/
mSm.mSmHandler = null;
mSm = null;
mMsg = null;
mLogRecords.cleanup();
mStateStack = null;
mTempStateStack = null;
mStateInfo.clear();
mInitialState = null;
mDestState = null;
mDeferredMessages.clear();
mHasQuit = true;
}
複製代碼
當 destState == mQuittingState
語句成立,將回調 StateMachine.onQuitting
函數,以後將執行 cleanupAfterQuitting
進行清理操做。清理操做中,會將線程清空,和其餘數據變量清空,而若是 destState == mHaltingState
成立,StateMachine
將不執行任何的清理操做,經過回調 onHalting
函數來通知狀態機退出。
Android
裏面的這個 StateMachine
狀態機在不少源碼中都有涉及,代碼也很簡單,沒有什麼太大的難度,但願以上的總結能幫各位看官理解 StateMachine
源碼的含義,而且能基於它,開發更多個性化的功能