Android StateMachine 狀態機分析
之前就有寫過一篇文章來學習狀態機:狀態機學習。
在之后的工作中多次用到了 StateMachine 狀態機,簡單記錄其原理。
StateMachine 類位于 Android 源碼中的,路徑是 frameworks/base/core/java/com/android/internal/util/StateMachine.java ,從路徑名可以看出,這是一個工具類,該類對狀態機進行了封裝,方便使用。
在安卓源碼中進行搜索,會發現很多類用到了狀態機,比如: WifiStateMachine 、 A2dpStateMachine 、 NsdStateMachine 、 P2dStateMachine 等等,它們都直接或間接的繼承了 StateMachine 類。可見,狀態機的思想在安卓源碼中得到了廣泛使用。
在我們的應用開發中也可以使用源碼中的 StateMachine 類,只要從源碼中把 StateMachine 和 State 類拷貝到我們的工程目錄就可以使用了,對于其 import 的一些類,在我們的開發環境中也全都有,不用擔心。
如果你對狀態機有那么一絲絲的了解,那么學習 StateMachine 類最好的資料就是它的注釋了,解釋的一清二楚。
簡單使用闡述
Android 中的狀態機是一個分層的消息處理機制,每一層都會有一到多個節點,而狀態機的消息就是在這些節點之間流轉處理,如下結構所示:
// 狀態機分層結構
mP0
/ \
mP1 mS0
/ \
mS2 mS1
/ \ \
mS3 mS4 mS5 ---> initial state 初始節點
而節點就是 State 類,它實現了 IState 接口,除了 enter 、 exit 方法外,還有 processMessage 方法,表示用來處理節點的消息。若返回 HANDLED 則表示消息處理完成,若返回 NOT_HANDLED 則表示消息沒有處理。
構造狀態機
在我們使用 StateMachine 之前,要構造好所需的狀態分層結構。通過 addState 方法來向狀態機中添加節點,例如如下的狀態結構,mS1 和 mS2 節點有公共的父節點 mP1,同時還有一個孤立的節點 mP2。
// 狀態分層結構設定
mP1 mP2
/ \
mS2 mS1
// 構造狀態機結構代碼
addState(mP1);
addState(mS1, mP1);
addState(mS2, mP1);
addState(mP2);
addState 方法添加節點時,還能指定其父節點添加。
當我們構造完了狀態機時,還需要指定其中一個節點為啟動點,消息從啟動點開始處理,通過 setInitialState 方法來指定啟動點,最后通過 start 方法啟動狀態機。
狀態機消息處理
當構造完想要的狀態機結構時,就是對狀態機內部消息流轉的處理了。
當 start 狀態機時,狀態機的第一個動作就是調用節點的 enter 方法,不過,它調用的是指定的啟動點的最遠的父節點的 enter 方法,然后再是次一級的父節點的 enter 方法,最后才是啟動點的 enter 方法,就如同上面的結構所示,先調用 mP1 點,然后才是 mS1 點的方法,此時 mS1 節點就是狀態機的當前對外的點。由此可見,當啟動點進入 enter 狀態時,它的父節點,直至最頂層的父節點都進入了 enter 狀態了。
狀態機中的每個節點都 0 個或 1 個父節點,當子節點不能處理當前消息時,它可以通過返回 NOT_HANDLED 將當前消息傳遞給其父節點來處理。如果一個消息從未被處理過,那么 unhandledMessage 方法將會被調用給最后一次機會來處理該消息。
除此之外,節點還可以通過 transitionTo 方法將當前節點轉移至另外一個新的節點。
mP0
/ \
mP1 mS0
/ \
mS2 mS1
/ \ \
mS3 mS4 mS5 ---> initial state
例如,當 mS5 處理消息,想要將當前節點轉移至 mS4 時,那么它會先找到 mS5 和 mS4 最近的公有父節點 mP1。然后,除了這個最近的公有父節點 mP1 以及它的上層節點外,mS5 進入 enter 狀態時啟動的那些父節點都會退出調用 exit 方法。最后再由 mP1 節點下的節點調用 enter 方法直到新節點 mS4 調用了 enter 方法。
也就是說,從 mS5 到 mS4 狀態的轉變,先是 mS5、mS1 調用了 exit 方法,再是 mS2、mS4 調用了 enter 方法,這就是狀態機中節點發生狀態轉移時的調用過程。
除此之外,節點還可以調用 deferMessage 和 sendMessageAtFroneOfQueue 方法。 deferMessage 方法使得消息存儲在消息隊列中,當狀態轉移到新節點時才會處理,而 sendMessageAtFrontOfQueue 方法則是將消息放置到消息隊列的頭部。
當狀態機的所有消息都完成時,可以調用 transitionToHaltingState 方法來將狀態機處理停止狀態。此時,狀態機將轉移到 HaltingState 停止狀態,并調用 halting 方法。隨后收到的所有消息都只是會調用 haltedProcessMessage 方法來處理了。
若想要完全的停止狀態機,則可以使用 quit 或者 quitNow 方法來處理。
以上就是狀態機對于消息處理的過程,長篇的文字說明還是不如代碼來的直觀,這里就不貼完整的代碼了
狀態機實現原理分析
如果我們在初始化狀態機時只是傳遞了一個名字,而沒有傳遞 Looper 或者 Handler 之類的消息循環,那么狀態機默認就是啟用其內部的一個線程 HandlerThread 。
protected StateMachine(String name) {
mSmThread = new HandlerThread(name); // 創建 HandlerThread 線程
mSmThread.start();
Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
// 將消息循環 Looper 與 Handler 進行綁定
private void initStateMachine(String name, Looper looper) {
mName = name;
mSmHandler = new SmHandler(looper, this);
}
構造狀態及時,在其內部開啟了一個線程,并將其消息循環 Looer 傳遞給了 SmHandler 對象,而 SmHandler 對象就是狀態機中最主要的用來派發消息事件和切換狀態的了,它派發的消息都是在 HandlerThread 線程進行處理的。
同時,SmHandler 內部有兩個數組,用來保存狀態機中的鏈式狀態關系,分別是 mStateStack 和 mTempStateStack 變量。當狀態機完成啟動時,就會通過上面來個變量來保存節點信息。
而狀態機的消息處理,內部也是通過 SmHandler 來處理轉發的。
具體的實現,建議參考這篇文章:http://blog.csdn.net/yangwen123/article/details/10591451,講的實在太詳細了,拜讀了多遍也不敢說寫的能比它更清楚。
參考
1、http://blog.csdn.net/yangwen123/article/details/10591451
來自:http://www.glumes.com/android-statemachine-analysis/