Android StateMachine與State模式
一 State模式
意圖:
允許一個對象在其內部狀態改變時改變它的行爲。對象看起來似乎修改了它的類。(Objects for States)對象內部狀態決定行爲方式,對象狀態改變行爲方式改變;但這裏爲何要強調內部狀態,外部狀態改變也同樣會影響行爲方式的改變,通常外部狀態的改變都會反映到內部狀態上來。Command模式是將命令請求封裝成一個爲對象,將不同的請求對象參數化以達到同樣的調用執行不同的命令;同樣State模式是將對象的狀態封裝成一個對象,是在不同的狀態下同樣的調用執行不同的操作。
適用性:
l 一個對象的行爲取決於它的狀態,並且它必須在運行時刻根據狀態來改變行爲;
l 一個操作中含有龐大的多分支條件語句,且這些分支依賴於該對象的狀態;
根據對象的狀態改變行爲採用了分支語句的實現方式,解決如此多的分支語句,
將分支語句放入一個類中進行處理,即將對象的狀態封裝成爲一個獨立對象;
結構:
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,結構都很相似;
這些地方都屬於數據連接相關,其中狀態較多,中間連接過程和處理比較複雜。