android狀態機statemachine詳解
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
分類: android應用分析 2013-08-13 07:18 1749人閱讀 評論(1) 收藏
先說兩句題外話,很感謝android,在這裡能看到很多優秀的程式碼。同時也感覺到外面的工程師真的很厲害,都是java人家就能寫出這麼牛的東西。感慨之下就有了些思考:我們絕大多數人只要把那些牛人已經創造出來的牛逼的東西,記住並且弄懂就是一件非常不錯的事情,至少能衣食無憂。:-D 讀書的時候需要經常做題,在理解的基礎上記住解題方法基本就能很牛了,事實上高考中考絕大多數都是已經有過的題型,能做到前面所說的應該能進入不錯的學校。工作後,慢慢也發現很多了不起的技術,都是在國外已經發展的很成熟基礎上學習過來的。作為一個普通人,還是不要天天談創新的好,hold不住,把基礎的東西記住掌握即可。說了一堆,也算聊以自慰。
我們知道類的成員可以分為兩種:方法和屬性。大多數情況下,對於一個狀態,比如某數大於0,類的方法都只能做出一種對應的操作,並且類的本身並不考慮外部狀態。android的狀態機就屬於大多數之後的那一小部分。對於某個事件,或者更準確的說,某一個訊息,在不同的狀態下能做出不同的操作。並且android狀態機中的狀態是繼承的,就像資料結構中的樹一樣,如果當前節點(狀態)不能對這個事件做出響應,就會到父節點繼續判斷並且做出響應,在下面的講述中,我們稱這個為狀態路徑,而對於所有狀態稱為狀態樹。這一句話已經從整體上對狀態機進行了概括,記住這些對後面的理解很有好處。
State,狀態機中的狀態封裝類,這個類主要是實現了IState介面。其中有狀態的基本方法,enter,exit以及訊息處理方法processMessage。enter方法在狀態機轉入這個狀態中會進行呼叫,exit方法在狀態機轉出這個方法時候會呼叫。這裡對於一個很簡單的類,google使用了介面屬性,說說自己的理解。介面中的方法都是公有方法,並且只能宣告常量。將主要方法都放在介面中宣告,一方面限制了方法的定義,一方面也突出了這個類主要就是擁有某種功能。另外在State裡面,聲明瞭一個protect型別的構造方法,這樣其他類就不可以直接宣告state類的物件。state在狀態機statemachine類裡面是以StateInfo型別使用的,這個基本不影響訪問。
statemachine裡面主要工作都是由SmHandler類來完成的,statemachine本身絕大多數方法都是都是對SmHandler方法的再次封裝。另外為了能夠做到及時響應主執行緒的訊息,又聲明瞭一個HandlerThread,主要任務都是在這個執行緒裡面實現的。
現在直接去看SmHandler類吧,其最主要的方法就是handleMessage。該方法的主要是三大模組,第一個訊息處理,或者說是分配到對應的狀態再有對應的狀態進行處理比較合適,第二個狀態的初始化,大概可以理解成執行初始化狀態路徑上每個狀態的enter方法,第三個執行狀態轉移,即更新狀態樹。
- if (mIsConstructionCompleted) {
- /** Normal path */
- 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();//第三個執行狀態轉移
首先去看下processMsg方法
[java] view plain copy print ?- while (!curStateInfo.state.processMessage(msg)) {
- /**
- * Not processed
- */
- curStateInfo = curStateInfo.parentStateInfo;
- if (curStateInfo == null) {
- /**
- * No parents left so it's not handled
- */
- mSm.unhandledMessage(msg);
- break;
- }
- if (mDbg) {
- Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
- }
- }
從這段程式碼中(!curStateInfo.state.processMessage(msg))就說明了:如果當前狀態執行完processMessage方法返回了false,也就是對當前訊息NOT_HANDLED,那麼就會持續呼叫這個狀態的父狀態執行方法。一般終有一個狀態能夠處理訊息的,如果真的沒有處理,會記錄到unhandledMessage方法裡面的。
接下來先看下狀態轉移performTransitions方法,首先碰到mDestState,這是SmHandler裡面的一個標記,指向當前狀態路徑最下面的一個狀態,後面都是父狀態。可以在其他時候呼叫private final void transitionTo(IState destState)方法(外面的介面方法)改變當前mDestState的指向。這樣處理完當前訊息之後,performTransitions就會根據最新的mDestState來進行狀態路徑切換。狀態切換有點像樹的遍歷一樣,並不是回到根節點在進行搜尋到新的節點,而是會回到原來的mDestState和最新的mDestState最近的一個共同祖先,然後在走向新的mDestState狀態。進行狀態切換主要是執行原狀態路徑上需要退出的狀態的exit()方法,和新的狀態路徑上的enter()方法。
最後看下狀態機的初始化invokeEnterMethods,這個直接看狀態機的例項比較方便。看一個簡單一些的應用,藍芽APK裡面關於耳機和電話連線處理的HeadsetStateMachine類,其構造方法中,關於狀態機的程式碼如下:
- addState(mDisconnected);
- addState(mPending);
- addState(mConnected);
- addState(mAudioOn);
- setInitialState(mDisconnected);
以上兩塊程式碼,第一塊是建立本狀態機的整個框架,就相當於建立整個狀態樹,第二個是設定初始化狀態。再看看HeadsetStateMachine中的靜態程式碼塊
[java] view plain copy print ?- static HeadsetStateMachine make(HeadsetService context) {
- Log.d(TAG, "make");
- HeadsetStateMachine hssm = new HeadsetStateMachine(context);
- hssm.start();
- return hssm;
- }
以上兩塊程式碼,第一塊是建立本狀態機的整個框架,就相當於建立整個狀態樹,第二個是設定初始化狀態。再看看HeadsetStateMachine中的靜態程式碼塊
這裡面有一個start()方法,從這個方法開始,狀態機開始運作,包括分配記憶體,根據初始化狀態設定初始化狀態路徑,這一點主要在setupInitialStateStack方法中執行,依次執行狀態路徑上每個狀態的enter方法,這個使用了訊息機制sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));,最終就在本段開頭的invokeEnterMethods方法中執行。
到這裡狀態機主要內容基本講解完畢,貌似絕大多數都需要記憶,記住了感覺就理解到了。:-D 有點像本文開頭說的。初一看感覺沒有什麼,但是如果想象下你有一個這樣的需求,耳機和手機的狀態一直在切換,你會採用什麼方式去做,在考慮了很多之後會感覺狀態機真的是一個很厲害的東西。:-D 接下來附上android原始碼中的demo,為了方便理解,筆者將輸出增加了一些空行,多餘的空行不是demo列印的。
- class Hsm1 extends StateMachine {
- private static final String TAG = "hsm1";
- public static final int CMD_1 = 1;
- public static final int CMD_2 = 2;
- public static final int CMD_3 = 3;
- public static final int CMD_4 = 4;
- public static final int CMD_5 = 5;
- public static Hsm1 makeHsm1() {
- Log.d(TAG, "makeHsm1 E");
- Hsm1 sm = new Hsm1("hsm1");
- sm.start();
- Log.d(TAG, "makeHsm1 X");
- return sm;
- }
- Hsm1(String name) {
- super(name);
- Log.d(TAG, "ctor E");
- // Add states, use indentation to show hierarchy
- addState(mP1);
- addState(mS1, mP1);
- addState(mS2, mP1);
- addState(mP2);
- // Set the initial state
- setInitialState(mS1);
- Log.d(TAG, "ctor X");
- }
- class P1 extends State {
- @Override public void enter() {
- Log.d(TAG, "mP1.enter");
- }
- @Override public boolean processMessage(Message message) {
- boolean retVal;
- Log.d(TAG, "mP1.processMessage what=" + message.what);
- switch(message.what) {
- case CMD_2:
- // CMD_2 will arrive in mS2 before CMD_3
- sendMessage(obtainMessage(CMD_3));
- deferMessage(message);
- transitionTo(mS2);
- retVal = HANDLED;
- break;
- default:
- // Any message we don't understand in this state invokes unhandledMessage
- retVal = NOT_HANDLED;
- break;
- }
- return retVal;
- }
- @Override public void exit() {
- Log.d(TAG, "mP1.exit");
- }
- }
- class S1 extends State {
- @Override public void enter() {
- Log.d(TAG, "mS1.enter");
- }
- @Override public boolean processMessage(Message message) {
- Log.d(TAG, "S1.processMessage what=" + message.what);
- if (message.what == CMD_1) {
- // Transition to ourself to show that enter/exit is called
- transitionTo(mS1);
- return HANDLED;
- } else {
- // Let parent process all other messages
先說兩句題外話,很感謝android,在這裡能看到很多優秀的程式碼。同時也感覺到外面的工程師真的很厲害,都是java人家就能寫出這麼牛的東西。感慨之下就有了些思考:我們絕大多數人只要把那些牛人已經創造出來的牛逼的東西,記住並且弄懂就是一件非常不錯的事情,至少能衣食無憂。:-D 讀書的時候需要經常做題,在理解的基礎上記住解題方法基本就能很牛了,事實上高考中考絕大多數都是已經有過的題型,能做到前面所說的應該能進入不錯的學校。工作後,慢慢也發現很多了不起的技術,都是在國外已經發展的很成熟基礎上學習過來的。作為一個普通人,還是不要天天談創新的好,hold不住,把基礎的東西記住掌握即可。說了一堆,也算聊以自慰。
我們知道類的成員可以分為兩種:方法和屬性。大多數情況下,對於一個狀態,比如某數大於0,類的方法都只能做出一種對應的操作,並且類的本身並不考慮外部狀態。android的狀態機就屬於大多數之後的那一小部分。對於某個事件,或者更準確的說,某一個訊息,在不同的狀態下能做出不同的操作。並且android狀態機中的狀態是繼承的,就像資料結構中的樹一樣,如果當前節點(狀態)不能對這個事件做出響應,就會到父節點繼續判斷並且做出響應,在下面的講述中,我們稱這個為狀態路徑,而對於所有狀態稱為狀態樹。這一句話已經從整體上對狀態機進行了概括,記住這些對後面的理解很有好處。
State,狀態機中的狀態封裝類,這個類主要是實現了IState介面。其中有狀態的基本方法,enter,exit以及訊息處理方法processMessage。enter方法在狀態機轉入這個狀態中會進行呼叫,exit方法在狀態機轉出這個方法時候會呼叫。這裡對於一個很簡單的類,google使用了介面屬性,說說自己的理解。介面中的方法都是公有方法,並且只能宣告常量。將主要方法都放在介面中宣告,一方面限制了方法的定義,一方面也突出了這個類主要就是擁有某種功能。另外在State裡面,聲明瞭一個protect型別的構造方法,這樣其他類就不可以直接宣告state類的物件。state在狀態機statemachine類裡面是以StateInfo型別使用的,這個基本不影響訪問。
statemachine裡面主要工作都是由SmHandler類來完成的,statemachine本身絕大多數方法都是都是對SmHandler方法的再次封裝。另外為了能夠做到及時響應主執行緒的訊息,又聲明瞭一個HandlerThread,主要任務都是在這個執行緒裡面實現的。
現在直接去看SmHandler類吧,其最主要的方法就是handleMessage。該方法的主要是三大模組,第一個訊息處理,或者說是分配到對應的狀態再有對應的狀態進行處理比較合適,第二個狀態的初始化,大概可以理解成執行初始化狀態路徑上每個狀態的enter方法,第三個執行狀態轉移,即更新狀態樹。
- if (mIsConstructionCompleted) {
- /** Normal path */
- 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();//第三個執行狀態轉移
首先去看下processMsg方法
[java] view plain copy print ?- while (!curStateInfo.state.processMessage(msg)) {
- /**
- * Not processed
- */
- curStateInfo = curStateInfo.parentStateInfo;
- if (curStateInfo == null) {
- /**
- * No parents left so it's not handled
- */
- mSm.unhandledMessage(msg);
- break;
- }
- if (mDbg) {
- Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
- }
- }
從這段程式碼中(!curStateInfo.state.processMessage(msg))就說明了:如果當前狀態執行完processMessage方法返回了false,也就是對當前訊息NOT_HANDLED,那麼就會持續呼叫這個狀態的父狀態執行方法。一般終有一個狀態能夠處理訊息的,如果真的沒有處理,會記錄到unhandledMessage方法裡面的。
接下來先看下狀態轉移performTransitions方法,首先碰到mDestState,這是SmHandler裡面的一個標記,指向當前狀態路徑最下面的一個狀態,後面都是父狀態。可以在其他時候呼叫private final void transitionTo(IState destState)方法(外面的介面方法)改變當前mDestState的指向。這樣處理完當前訊息之後,performTransitions就會根據最新的mDestState來進行狀態路徑切換。狀態切換有點像樹的遍歷一樣,並不是回到根節點在進行搜尋到新的節點,而是會回到原來的mDestState和最新的mDestState最近的一個共同祖先,然後在走向新的mDestState狀態。進行狀態切換主要是執行原狀態路徑上需要退出的狀態的exit()方法,和新的狀態路徑上的enter()方法。
最後看下狀態機的初始化invokeEnterMethods,這個直接看狀態機的例項比較方便。看一個簡單一些的應用,藍芽APK裡面關於耳機和電話連線處理的HeadsetStateMachine類,其構造方法中,關於狀態機的程式碼如下:
- addState(mDisconnected);
- addState(mPending);
- addState(mConnected);
- addState(mAudioOn);
- setInitialState(mDisconnected);
以上兩塊程式碼,第一塊是建立本狀態機的整個框架,就相當於建立整個狀態樹,第二個是設定初始化狀態。再看看HeadsetStateMachine中的靜態程式碼塊
[java] view plain copy print ?- static HeadsetStateMachine make(HeadsetService context) {
- Log.d(TAG, "make");
- HeadsetStateMachine hssm = new HeadsetStateMachine(context);
- hssm.start();
- return hssm;
- }
以上兩塊程式碼,第一塊是建立本狀態機的整個框架,就相當於建立整個狀態樹,第二個是設定初始化狀態。再看看HeadsetStateMachine中的靜態程式碼塊
這裡面有一個start()方法,從這個方法開始,狀態機開始運作,包括分配記憶體,根據初始化狀態設定初始化狀態路徑,這一點主要在setupInitialStateStack方法中執行,依次執行狀態路徑上每個狀態的enter方法,這個使用了訊息機制sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));,最終就在本段開頭的invokeEnterMethods方法中執行。
到這裡狀態機主要內容基本講解完畢,貌似絕大多數都需要記憶,記住了感覺就理解到了。:-D 有點像本文開頭說的。初一看感覺沒有什麼,但是如果想象下你有一個這樣的需求,耳機和手機的狀態一直在切換,你會採用什麼方式去做,在考慮了很多之後會感覺狀態機真的是一個很厲害的東西。:-D 接下來附上android原始碼中的demo,為了方便理解,筆者將輸出增加了一些空行,多餘的空行不是demo列印的。
- class Hsm1 extends StateMachine {
- private static final String TAG = "hsm1";
- public static final int CMD_1 = 1;
- public static final int CMD_2 = 2;
- public static final int CMD_3 = 3;
- public static final int CMD_4 = 4;
- public static final int CMD_5 = 5;
- public static Hsm1 makeHsm1() {
- Log.d(TAG, "makeHsm1 E");
- Hsm1 sm = new Hsm1("hsm1");
- sm.start();
- Log.d(TAG, "makeHsm1 X");
- return sm;
- }
- Hsm1(String name) {
- super(name);
- Log.d(TAG, "ctor E");
- // Add states, use indentation to show hierarchy
- addState(mP1);
- addState(mS1, mP1);
- addState(mS2, mP1);
- addState(mP2);
- // Set the initial state
- setInitialState(mS1);
- Log.d(TAG, "ctor X");
- }
- class P1 extends State {
- @Override public void enter() {
- Log.d(TAG, "mP1.enter");
- }
- @Override public boolean processMessage(Message message) {
- boolean retVal;
- Log.d(TAG, "mP1.processMessage what=" + message.what);
- switch(message.what) {
- case CMD_2:
- // CMD_2 will arrive in mS2 before CMD_3
- sendMessage(obtainMessage(CMD_3));
- deferMessage(message);
- transitionTo(mS2);
- retVal = HANDLED;
- break;
- default:
- // Any message we don't understand in this state invokes unhandledMessage
- retVal = NOT_HANDLED;
- break;
- }
- return retVal;
- }
- @Override public void exit() {
- Log.d(TAG, "mP1.exit");
- }
- }
- class S1 extends State {
- @Override public void enter() {
- Log.d(TAG, "mS1.enter");
- }
- @Override public boolean processMessage(Message message) {
- Log.d(TAG, "S1.processMessage what=" + message.what);
- if (message.what == CMD_1) {
- // Transition to ourself to show that enter/exit is called
- transitionTo(mS1);
- return HANDLED;
- } else {
- // Let parent process all other messages