Android學習 StateMachine與State模式

Android學習 StateMachine與State模式 html

一 State模式

意圖: 數據結構

  容許一個對象在其內部狀態改變時改變它的行爲。對象看起來彷佛修改了它的類。(Objects for States) 框架

  對象內部狀態決定行爲方式,對象狀態改變行爲方式改變;但這裏爲什麼要強調內部狀態, 異步

外部狀態改變也一樣會影響行爲方式的改變,一般外部狀態的改變都會反映到內部狀態上來。 函數

  Command模式是將命令請求封裝成一個爲對象,將不一樣的請求對象參數化以達到一樣的調用執行不一樣的命令; oop

一樣State模式是將對象的狀態封裝成一個對象,是在不一樣的狀態下一樣的調用執行不一樣的操做。 post

 

適用性: 學習

  l  一個對象的行爲取決於它的狀態,而且它必須在運行時刻根據狀態來改變行爲; ui

  l  一個操做中含有龐大的多分支條件語句,且這些分支依賴於該對象的狀態; this

    根據對象的狀態改變行爲採用了分支語句的實現方式,解決如此多的分支語句,

    將分支語句放入一個類中進行處理,即將對象的狀態封裝成爲一個獨立對象;

結構:

      

    

  Context:維護一個State的子類實例,這個實例定義當前的狀態;

  State:定義一個接口以封裝與Context的一個特定狀態相關的行爲;

 

Context與State交互:

  l  Context將與狀態相關的請求委託給當前的ConcreteState處理;

  l  Context可將自身做爲一個參數傳遞給處理該請求的狀態,使得狀態對象在必要的時訪問Context;

  l  Context是客戶使用的主要接口,客戶可用對象來配置一個Context,一旦一個Context配置完畢,

    它的客戶再也不須要直接與狀態對象打交道;

  l  Context或ConcreteState子類均可以決定哪一個狀態是另外哪個的後繼者,以及是在何種條件下進行轉換;

 

理解:

  將對象的狀態封裝成一個對象,特定的狀態下具體的操做行爲不一樣,因此將對象的狀態封裝成一個對象目地,

是爲了獲得不一樣的行爲方式;封裝對象的狀態是將對象的狀態與行爲封裝在一塊兒;能夠解決龐大的分支語句帶來

程序閱讀性差和不便於進行擴展問題,使整個結構變得更加清晰明瞭,下降程序管理的複雜性提升靈活度。

  解決不斷根據內部變量屬性改變來決定狀態,這樣對於複雜多狀態的維護變得相對麻煩;而要保持對象的狀態一致性,

使狀態是做爲一個對象進行改變,從Context角度來理解,狀態的改變就是原子性——設置一個新的狀態State對象便可,

是特定狀態內部變量屬性更改達到一致性。

  誰定義狀態轉換呢?  一是由Context對象來改變,容易控制對象的狀態,保持對象之間徹底獨立;

二是由State對象來改變設定其下一個狀態,很容易控制狀態改變,保證狀態的連續性;由Context來改變

狀態不知道什麼時候該去改變狀態比較合適,不知道當前狀態處於什麼狀況,就須要等待當前狀態執行結果真後決定;

由State對象自己來改變,則須要Context提供相應的接口來設定當前的狀態,而且須要知道下一個狀態對象是誰,

至少是一個狀態須要被瞭解,形成各狀態對象之間產生了很強的的依賴性,而且在下一個狀態不肯定的狀況下,

在某種狀況下才被觸發,State對象自己很難去決定下一個對象是誰,你想用State來監聽者些狀況的觸發嗎?

NO不可能的;因此須要在具體問題中去權衡選擇。

  State對象的建立與銷燬,一是當須要State對象建立它切換後銷燬它;二是提早建立狀態切換不銷燬。

對於不一樣的語言和使用環境,採起的策略會不一樣。

       下面來看一下Android平臺中對於State模式的應用。

 

二 Android中StateMachine機制

  對與State改變切換這種常見經常使用的處理,只是各個平臺框架中處理的方法不一樣,

這種在處理多狀態較爲複雜的大部分場景都能見到的策略——狀態機(StateMachine)。

在Android中使用的了StateMachine機制就是一個State模式的應用, StateMachine是很是的強大和精妙。

  下面先簡單看一下這個StateMachine。

 

StateMachine類做用:

  The state machine defined here is a hierarchical state machine which processes

messages and can have states arranged hierarchically.

  這裏的狀態機是一個分層處理消息的狀態機,而且是可以有分層排列狀態。

下面經過這個類層次的結構來:

 

    

這樣就構成了State模式:

       Context——StateMachine

       State   ——State

 

  StateMachine的構造函數都是protected類型,不能實例化;都是由其子類進行初始化操做;

複製代碼
protected StateMachine(String name) 
{
  mSmThread = new HandlerThread(name);
  mSmThread.start();
  Looper looper = mSmThread.getLooper();
  initStateMachine(name, looper);
}
複製代碼

 

  可是這個類具體是怎麼構成上述的層次結構狀態和層次消息處理仍然不明確。

  下面繼續看一下這個類各個模塊的做用。

 

三 Android 中StateMachine模塊分析

 

1 State Machine各個模塊做用

 

State:

複製代碼
public class State implements IState
{
  protected State() {}
  public void enter() {}
  public void exit() {}
  public boolean processMessage(Message msg) {}
  public String getName() {}
}
複製代碼

  狀態的基類,stateMachine中的狀態都是由State派生而來,構造函數protected,不能實例化;

 

StateMachine三個內部類:

ProcessedMessageInfo:  保存已處理消息的信息

複製代碼
public static class ProcessedMessageInfo 
{
  private int what;                    //用戶定義消息標識
  private State state;                //處理當前消息的狀態
  private State orgState;           //消息未被處理前當前的狀態
}
複製代碼

 

ProcessedMessages:

  存儲StateMachine最近處理的一些消息,須要保存最近處理的消息條數默認20,能夠用戶本身設定最大數目。

複製代碼
private static class ProcessedMessages {
  private static final int DEFAULT_SIZE = 20;
  private Vector<ProcessedMessageInfo> mMessages = 
    new Vector<ProcessedMessageInfo>();
  private int mMaxSize = DEFAULT_SIZE;
  private int mOldestIndex = 0;
  private int mCount = 0;
}
複製代碼

 

SmHandler有三個內部類:

StateInfo:存儲當前State,和其parentState,以及是否激活狀態;用來構建樹形層次結構模型

複製代碼
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;
}
複製代碼

 

HaltingState與QuittingState:

       都是State的 派生類,用於在狀態中止和放棄以後處理的一些事情;都重寫了ProcessMessage方法,

在StateMachine沒有實際行動僅僅保留用於擴展。

       整個SmHandle是消息處理派發和狀態控制切換的核心,運行在單獨的線程上。

SmHandle:

  數據成員很多,列出其中關鍵的一些;

複製代碼
private static class SmHandler extends Handler 
{
  /** The current message */
  private Message mMsg;

  /** A list of messages that this state machine has processed */
  private ProcessedMessages mProcessedMessages =
    new ProcessedMessages();

  /** Stack used to manage the current hierarchy of states */
  private StateInfo mStateStack[];

  /** The map of all of the states in the state machine */
  private HashMap<State, StateInfo> mStateInfo =
    new HashMap<State, StateInfo>();

  /** The initial state that will process the first message */
  private State mInitialState;

  /** The destination state when transitionTo has been invoked */
  private State mDestState;
}
複製代碼

 

SmHandle是構建StateMachine的核心,運行在獨立的線程上,有三個功能:

  • 創建樹形層次結構存儲State;
  • 狀態機的StateStack創建和狀態切換;
  • 消息處理和派發;

  下面看看是如何完成這三個功能的:

 

2 創建樹形層次結構存儲State

 

       在構成一個狀態機前須要肯定當前都多少狀態,須要將這些狀態集中起來進行管理。

StateMachine提供了這樣一個protected類型方法 AddState來將狀態

  添加到狀態機中。看看這個函數:

  protected final void addState(State state, State parent) {

    mSmHandler.addState(state, parent);

  }實際上仍是SmHandle來工做;Go on……

 

複製代碼
/****************************************************
* state:    加入state machine的State
* parent:    the parent of state
****************************************************/
private final StateInfo addState(State state, State parent) 
{
  StateInfo parentStateInfo = null;
  if (parent != null) {
    //獲取當前狀態parent詳細信息 StateInfo
    parentStateInfo = mStateInfo.get(parent);
    if (parentStateInfo == null) {
      //當前狀態父狀態未加入到StateMachine中,
      //遞歸先加入其Parent State
      parentStateInfo = addState(parent, null);
    }
  }

  //判斷當前狀態是否加入到 StateMachine層次結構中
  StateInfo stateInfo = mStateInfo.get(state);
  if (stateInfo == null) {
    //建立State詳細信息對象,將其加入到StateMachine層次結構中
    stateInfo = new StateInfo();
    mStateInfo.put(state, stateInfo);
  }

  //驗證咱們沒有加入相同的狀態,在兩個不一樣層次,不然異常
  if ((stateInfo.parentStateInfo != null) &&
    (stateInfo.parentStateInfo != parentStateInfo)) {
      throw new RuntimeException("state already added");
  }

  //完善當前狀態信息
  stateInfo.state = state;
  stateInfo.parentStateInfo = parentStateInfo;
  stateInfo.active = false;
  return stateInfo;
}
複製代碼

 

因此StateMachine類中:

    StateInfo就是包裝State組成一個Node,創建State的父子關係;

    private HashMap<State, StateInfo> mStateInfo =

      new HashMap<State, StateInfo>();

    就是用來保存State Machine中的State—嚴格按照樹形層次結構組織;

複製代碼
例如:  
  SmHandle sm;
  sm.addState(S0,null);
  sm.addState(S1,S0);
  sm.addState(S2,S0);
  sm.addState(S3,S1);
  sm.addState(S4,S1);
  sm.addState(S5,S2);
  sm.addState(S6,S2);
  sm.addState(S7,S2);
  setInitialState(S4);      //設置初始狀態
複製代碼

  獲得的狀態樹形層次結構以下:

 

                           

    

 

       樹形層次結構存儲State就是如此完成的:

              存儲數據結構:StateInfo以及HashMap<State, StateInfo> mStateInfo。

              方法:StateInfo addState(State state, State parent);

 

3 狀態機的StateStack創建和狀態切換

 

狀態機的StateStack創建:

  各狀態State加入到StateMachine,各條件初始化OK後,就能夠啓動狀態機了。

  StateMachine提供了方法:

  public void start() 
  {
    /** Send the complete construction message */
    mSmHandler.completeConstruction();
  }


  SmHandle:completeConstruction構建狀態機運行模型

複製代碼
//Complete the construction of the state machine.
private final void completeConstruction()
{
    //計算State繼承層次結構的最大深度以便建立運行時刻State Stack
    int maxDepth = 0;
    for (StateInfo si : mStateInfo.values()) {
           int depth = 0;
           for (StateInfo i = si; i != null; depth++) {
                  i = i.parentStateInfo;
           }
           if (maxDepth < depth) {
                  maxDepth = depth;
           }
    }

    //建立State Stack
    mStateStack = new StateInfo[maxDepth];
    mTempStateStack = new StateInfo[maxDepth];

    //根據當前mDestState(S5)按照其層次結構沿着其父子關係,
    //保存此條路徑上的StateInfo 存儲到State Stack中因而
    //例如:S0--S2—S5 存儲到mStateStack中
    setupInitialStateStack();

    //層次結構狀態構建完成調用mStateStack中State的enter方法
    //使mStateStack中的State 處於active狀態
    mIsConstructionCompleted = true;
    mMsg = obtainMessage(SM_INIT_CMD);
    invokeEnterMethods(0);

    //Perform any transitions requested by the enter methods
    performTransitions();      //待下面分析
}
複製代碼

  這裏創建State Stack是幹什麼用的呢?

State Stack裏面的元素結構是根據父子關係組成鏈式結構:S0——S2——S5;S5確定mDestState,

S2,S0都是其parentState;狀態是一種父子關係,那麼這兩個狀態之間存在某種關係;

State對應着行爲,這裏到底要幹什麼呢?

    在後面咱們能夠看到狀態行爲處理執行都是根據此mStateStack進行的。

 

狀態切換:

  StateMachine中提供了方法:  

protected final void transitionTo(IState destState) 
{
  mSmHandler.transitionTo(destState);
}此方法用來進行狀態切換;


  SmHandle提供的方法:

private final void transitionTo(IState destState) 
{
  // mDestState保存當前狀態 來處理消息;
  mDestState = (State) destState;
}

  而上面所提到的狀態切換:protected final void transitionTo(IState destState);

僅僅是改變了當前狀態mDestState,從StateStack創建這裏能夠看到和這個mDestState相關的還有mStateStack,

若是改變了mDestState,顯然這裏的mStateStack也是須要進行改變的,使mStateStack仍然是鏈式層次式結構。

  因此上面這個狀態切換其實並不算完整,還須要改變mStateStack;也就是mDestState改變時,

沒有同時改變 mStateStack,而是等到消息處理派發狀態Handle的時候,當前的狀態行爲處理完,

切換到下一個狀態,即消息處理完畢而後才進行mStateStack的更新。

這個是和狀態切換過程相關的:使狀態切換和mStateStack的更新獨立開來。

 

  狀態切換與數據處理過程是這樣的:先無論誰來改變State

 

     

      

 

  因此僅僅改變mDestState還不夠,還須要改變mStateStack

 

就是這個函數:performTransitions();

       先看看這樣一個例子,關係仍是上面的S0——S7:

       mStateStack中存儲:S0——S2——S5 mDestState爲S5 (棧頂)

       如今狀態切換爲S3,mDestState爲S3

       按照父子關係,mStateStack應該存儲有:S0——S1——S3

       那麼此時S5,S2都要出棧pop from mStateStack

       那咱們就是要找到一個點,讓S5,S2出棧;S3,S1進棧;

怎麼去執行,這就是這個performTransitions乾的事情。

  主要代碼以下:

複製代碼
//Do any transitions
private synchronized void performTransitions() 
{
  while (mDestState != null)
  {
    //當前狀態切換了 存在於mStateStack中的State須要改變
    //仍然按照鏈式父子關係來存儲
    //先從當前狀態S3找到 最近的被激活的parent狀態S0
    //未被激活的所有保存起來(S3,S1) 返回S0
    StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);

    //將mStateStack中 不屬於當前狀態(S3),
    //關係鏈上的State(S5,S2)退出(執行exit方法)
    invokeExitMethods(commonStateInfo);

    //將S3關係鏈 加入到棧中(S3,S1)
    int stateStackEnteringIndex = moveTempStateStackToStateStack();

    //將新加入到mStateStack中 未被激活的State激活(S3,S1)
    invokeEnterMethods(stateStackEnteringIndex);

    //將延遲的消息移動到消息隊列的前面,以便快速獲得處理               
    moveDeferredMessageAtFrontOfQueue();
  }
}
複製代碼

這樣整個狀態切換就完成了:

  切換當前狀態:mDestState;

  更新狀態棧:mStateStack;

  可是當前狀態的切換在StateMachine中並無明確,由於這只是一個狀態機負責狀態的管理和消息派發;

誰將負責狀態的切換仍是交由其子類決定;

 

4 消息處理和派發

 

  StateMachine處理的核心就是SmHandler,就是一個Handler,運行在單獨線程中。

Handler是用來異步處理派發消息,這裏使用Handler管理各個狀態,派發消息處理到各個狀態中去執行。

  狀態機準備OK後(狀態加入和狀態棧構建完成)就能夠執行某些行爲,接收消息進行處理,派發到當前狀態去執行。

看一下SmHandler中handleMessage是如何進行消息處理的。

 

消息接收:

       StateMachine提供了sendMessage等方法將消息加入到消息隊列中,固然都是交給SmHandler去處理的。

這就關乎Handler處理消息的機制了;

 

消息派發:    

複製代碼
public final void handleMessage(Message msg) 
{
  //處理當前消息到state中去處理
  processMsg(msg);

  //消息處理完畢狀態切換 更新mStateStack
  performTransitions();
}
複製代碼

 

 

複製代碼
private final void processMsg(Message msg)
{
  //派發消息到state中去處理
  StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
  while (!curStateInfo.state.processMessage(msg))
  {
    //當前狀態mDestState 未處理該消息,交給其parent state處理
    curStateInfo = curStateInfo.parentStateInfo;
    if (curStateInfo == null){
      //此消息未被當前層次狀態鏈處理
    }
  }
}
複製代碼

 

  到這裏看到創建狀態棧mStateStack的做用,用來支持進行鏈式的消息處理;(Chain of Responsibility)

因此這是一個比較強大的狀態機!

 

四 Android中StateMachine實例應用

  看一下Android 中WpsStateMachine對於StateMachine的應用。

WpsStateMachine做用:Manages a WPS connection。繼承自StateMachine。

       大體的類結構以下:

     

    

構造函數中:

複製代碼
WpsStateMachine(Context context, WifiStateMachine wsm, Handler target) 
{
  //初始化基類StateMachine
  super(TAG, target.getLooper());

  //添加狀態 創建樹形層次結構存儲
  addState(mDefaultState);
  addState(mInactiveState, mDefaultState);
  addState(mActiveState, mDefaultState);

  //設置初始狀態
  setInitialState(mInactiveState);
  //start the state machine
  start();
}
複製代碼

 

 

  其中具備的狀態:DefaultState、ActiveState、InactiveState(都是WpsStateMachine內部類);

這個層次就是DefaultState做爲parent,兩個children:ActiveState、InactiveState;

  在這些State派生類中的處理函數processMessage中可以看到transitionTo切換狀態;

  這裏狀態切換是由State完成。

 

後記:

這個StateMachine在什麼地方使用較多,能夠搜索一下代碼看到有:

  BluetoothAdapterStateMachine,BluetoothDeviceProfileState,

BluetoothProfileState,DataConnection,DhcpStateMachine,RilMessageDecoder,

WpsStateMachine,WifiStateMachine等等這些地方都用了到了StateMachine,結構都很類似;

  這些地方都屬於數據鏈接相關,其中狀態較多,中間鏈接過程和處理比較複雜。

(from: http://www.cnblogs.com/bastard/archive/2012/06/05/2536258.html

-v- 老窩 iwangyue.cn

相關文章
相關標籤/搜索