Android StateMachine 狀態機分析

ShaSamson 7年前發布 | 9K 次閱讀 安卓開發 Android開發 移動開發

之前就有寫過一篇文章來學習狀態機:狀態機學習。

在之后的工作中多次用到了 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/

 

 本文由用戶 ShaSamson 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!