1. 程式人生 > >android狀態機statemachine詳解

android狀態機statemachine詳解

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               
分類: android應用分析   1749人閱讀  評論(1)  收藏
  舉報 android狀態機詳解 statemachine android 狀態state

        先說兩句題外話,很感謝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方法,第三個執行狀態轉移,即更新狀態樹。

[java]  view plain copy print ?
  1. if (mIsConstructionCompleted) {  
  2.     /** Normal path */  
  3.     processMsg(msg);//第一個訊息處理  
  4. else if (!mIsConstructionCompleted &&  
  5.         (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {  
  6.     /** Initial one time path. */  
  7.     mIsConstructionCompleted = true;  
  8.     invokeEnterMethods(0);//第二個狀態的初始化  
  9. else {  
  10.     throw new RuntimeException("StateMachine.handleMessage: " +  
  11.                 "The start method not called, received msg: " + msg);  
  12. }  
  13. performTransitions();//第三個執行狀態轉移  

首先去看下processMsg方法

[java]  view plain copy print ?
  1. while (!curStateInfo.state.processMessage(msg)) {  
  2.     /** 
  3.      * Not processed 
  4.      */  
  5.     curStateInfo = curStateInfo.parentStateInfo;  
  6.     if (curStateInfo == null) {  
  7.         /** 
  8.          * No parents left so it's not handled 
  9.          */  
  10.         mSm.unhandledMessage(msg);  
  11.         break;  
  12.     }  
  13.     if (mDbg) {  
  14.         Log.d(TAG, "processMsg: " + curStateInfo.state.getName());  
  15.     }  
  16. }  

從這段程式碼中(!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類,其構造方法中,關於狀態機的程式碼如下:

[java]  view plain copy print ?
  1. addState(mDisconnected);  
  2. addState(mPending);  
  3. addState(mConnected);  
  4. addState(mAudioOn);  
  5.   
  6. setInitialState(mDisconnected);  

以上兩塊程式碼,第一塊是建立本狀態機的整個框架,就相當於建立整個狀態樹,第二個是設定初始化狀態。再看看HeadsetStateMachine中的靜態程式碼塊

[java]  view plain copy print ?
  1. static HeadsetStateMachine make(HeadsetService context) {  
  2.     Log.d(TAG, "make");  
  3.     HeadsetStateMachine hssm = new HeadsetStateMachine(context);  
  4.     hssm.start();  
  5.     return hssm;  
  6. }  

以上兩塊程式碼,第一塊是建立本狀態機的整個框架,就相當於建立整個狀態樹,第二個是設定初始化狀態。再看看HeadsetStateMachine中的靜態程式碼塊
這裡面有一個start()方法,從這個方法開始,狀態機開始運作,包括分配記憶體,根據初始化狀態設定初始化狀態路徑,這一點主要在setupInitialStateStack方法中執行,依次執行狀態路徑上每個狀態的enter方法,這個使用了訊息機制sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));,最終就在本段開頭的invokeEnterMethods方法中執行。
        到這裡狀態機主要內容基本講解完畢,貌似絕大多數都需要記憶,記住了感覺就理解到了。:-D 有點像本文開頭說的。初一看感覺沒有什麼,但是如果想象下你有一個這樣的需求,耳機和手機的狀態一直在切換,你會採用什麼方式去做,在考慮了很多之後會感覺狀態機真的是一個很厲害的東西。:-D 接下來附上android原始碼中的demo,為了方便理解,筆者將輸出增加了一些空行,多餘的空行不是demo列印的。

[java]  view plain copy print ?
  1. class Hsm1 extends StateMachine {  
  2.     private static final String TAG = "hsm1";  
  3.   
  4.     public static final int CMD_1 = 1;  
  5.     public static final int CMD_2 = 2;  
  6.     public static final int CMD_3 = 3;  
  7.     public static final int CMD_4 = 4;  
  8.     public static final int CMD_5 = 5;  
  9.   
  10.     public static Hsm1 makeHsm1() {  
  11.         Log.d(TAG, "makeHsm1 E");  
  12.         Hsm1 sm = new Hsm1("hsm1");  
  13.         sm.start();  
  14.         Log.d(TAG, "makeHsm1 X");  
  15.         return sm;  
  16.     }  
  17.   
  18.     Hsm1(String name) {  
  19.         super(name);  
  20.         Log.d(TAG, "ctor E");  
  21.   
  22.         // Add states, use indentation to show hierarchy  
  23.         addState(mP1);  
  24.             addState(mS1, mP1);  
  25.             addState(mS2, mP1);  
  26.         addState(mP2);  
  27.   
  28.         // Set the initial state  
  29.         setInitialState(mS1);  
  30.         Log.d(TAG, "ctor X");  
  31.     }  
  32.   
  33.     class P1 extends State {  
  34.         @Override public void enter() {  
  35.             Log.d(TAG, "mP1.enter");  
  36.         }  
  37.         @Override public boolean processMessage(Message message) {  
  38.             boolean retVal;  
  39.             Log.d(TAG, "mP1.processMessage what=" + message.what);  
  40.             switch(message.what) {  
  41.             case CMD_2:  
  42.                 // CMD_2 will arrive in mS2 before CMD_3  
  43.                 sendMessage(obtainMessage(CMD_3));  
  44.                 deferMessage(message);  
  45.                 transitionTo(mS2);  
  46.                 retVal = HANDLED;  
  47.                 break;  
  48.             default:  
  49.                 // Any message we don't understand in this state invokes unhandledMessage  
  50.                 retVal = NOT_HANDLED;  
  51.                 break;  
  52.             }  
  53.             return retVal;  
  54.         }  
  55.         @Override public void exit() {  
  56.             Log.d(TAG, "mP1.exit");  
  57.         }  
  58.     }  
  59.   
  60.     class S1 extends State {  
  61.         @Override public void enter() {  
  62.             Log.d(TAG, "mS1.enter");  
  63.         }  
  64.         @Override public boolean processMessage(Message message) {  
  65.             Log.d(TAG, "S1.processMessage what=" + message.what);  
  66.             if (message.what == CMD_1) {  
  67.                 // Transition to ourself to show that enter/exit is called  
  68.                 transitionTo(mS1);  
  69.                 return HANDLED;  
  70.             } else {  
  71.                 // 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方法,第三個執行狀態轉移,即更新狀態樹。

[java]  view plain copy print ?
  1. if (mIsConstructionCompleted) {  
  2.     /** Normal path */  
  3.     processMsg(msg);//第一個訊息處理  
  4. else if (!mIsConstructionCompleted &&  
  5.         (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {  
  6.     /** Initial one time path. */  
  7.     mIsConstructionCompleted = true;  
  8.     invokeEnterMethods(0);//第二個狀態的初始化  
  9. else {  
  10.     throw new RuntimeException("StateMachine.handleMessage: " +  
  11.                 "The start method not called, received msg: " + msg);  
  12. }  
  13. performTransitions();//第三個執行狀態轉移  

首先去看下processMsg方法

[java]  view plain copy print ?
  1. while (!curStateInfo.state.processMessage(msg)) {  
  2.     /** 
  3.      * Not processed 
  4.      */  
  5.     curStateInfo = curStateInfo.parentStateInfo;  
  6.     if (curStateInfo == null) {  
  7.         /** 
  8.          * No parents left so it's not handled 
  9.          */  
  10.         mSm.unhandledMessage(msg);  
  11.         break;  
  12.     }  
  13.     if (mDbg) {  
  14.         Log.d(TAG, "processMsg: " + curStateInfo.state.getName());  
  15.     }  
  16. }  

從這段程式碼中(!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類,其構造方法中,關於狀態機的程式碼如下:

[java]  view plain copy print ?
  1. addState(mDisconnected);  
  2. addState(mPending);  
  3. addState(mConnected);  
  4. addState(mAudioOn);  
  5.   
  6. setInitialState(mDisconnected);  

以上兩塊程式碼,第一塊是建立本狀態機的整個框架,就相當於建立整個狀態樹,第二個是設定初始化狀態。再看看HeadsetStateMachine中的靜態程式碼塊

[java]  view plain copy print ?
  1. static HeadsetStateMachine make(HeadsetService context) {  
  2.     Log.d(TAG, "make");  
  3.     HeadsetStateMachine hssm = new HeadsetStateMachine(context);  
  4.     hssm.start();  
  5.     return hssm;  
  6. }  

以上兩塊程式碼,第一塊是建立本狀態機的整個框架,就相當於建立整個狀態樹,第二個是設定初始化狀態。再看看HeadsetStateMachine中的靜態程式碼塊
這裡面有一個start()方法,從這個方法開始,狀態機開始運作,包括分配記憶體,根據初始化狀態設定初始化狀態路徑,這一點主要在setupInitialStateStack方法中執行,依次執行狀態路徑上每個狀態的enter方法,這個使用了訊息機制sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));,最終就在本段開頭的invokeEnterMethods方法中執行。
        到這裡狀態機主要內容基本講解完畢,貌似絕大多數都需要記憶,記住了感覺就理解到了。:-D 有點像本文開頭說的。初一看感覺沒有什麼,但是如果想象下你有一個這樣的需求,耳機和手機的狀態一直在切換,你會採用什麼方式去做,在考慮了很多之後會感覺狀態機真的是一個很厲害的東西。:-D 接下來附上android原始碼中的demo,為了方便理解,筆者將輸出增加了一些空行,多餘的空行不是demo列印的。

[java]  view plain copy print ?
  1. class Hsm1 extends StateMachine {  
  2.     private static final String TAG = "hsm1";  
  3.   
  4.     public static final int CMD_1 = 1;  
  5.     public static final int CMD_2 = 2;  
  6.     public static final int CMD_3 = 3;  
  7.     public static final int CMD_4 = 4;  
  8.     public static final int CMD_5 = 5;  
  9.   
  10.     public static Hsm1 makeHsm1() {  
  11.         Log.d(TAG, "makeHsm1 E");  
  12.         Hsm1 sm = new Hsm1("hsm1");  
  13.         sm.start();  
  14.         Log.d(TAG, "makeHsm1 X");  
  15.         return sm;  
  16.     }  
  17.   
  18.     Hsm1(String name) {  
  19.         super(name);  
  20.         Log.d(TAG, "ctor E");  
  21.   
  22.         // Add states, use indentation to show hierarchy  
  23.         addState(mP1);  
  24.             addState(mS1, mP1);  
  25.             addState(mS2, mP1);  
  26.         addState(mP2);  
  27.   
  28.         // Set the initial state  
  29.         setInitialState(mS1);  
  30.         Log.d(TAG, "ctor X");  
  31.     }  
  32.   
  33.     class P1 extends State {  
  34.         @Override public void enter() {  
  35.             Log.d(TAG, "mP1.enter");  
  36.         }  
  37.         @Override public boolean processMessage(Message message) {  
  38.             boolean retVal;  
  39.             Log.d(TAG, "mP1.processMessage what=" + message.what);  
  40.             switch(message.what) {  
  41.             case CMD_2:  
  42.                 // CMD_2 will arrive in mS2 before CMD_3  
  43.                 sendMessage(obtainMessage(CMD_3));  
  44.                 deferMessage(message);  
  45.                 transitionTo(mS2);  
  46.                 retVal = HANDLED;  
  47.                 break;  
  48.             default:  
  49.                 // Any message we don't understand in this state invokes unhandledMessage  
  50.                 retVal = NOT_HANDLED;  
  51.                 break;  
  52.             }  
  53.             return retVal;  
  54.         }  
  55.         @Override public void exit() {  
  56.             Log.d(TAG, "mP1.exit");  
  57.         }  
  58.     }  
  59.   
  60.     class S1 extends State {  
  61.         @Override public void enter() {  
  62.             Log.d(TAG, "mS1.enter");  
  63.         }  
  64.         @Override public boolean processMessage(Message message) {  
  65.             Log.d(TAG, "S1.processMessage what=" + message.what);  
  66.             if (message.what == CMD_1) {  
  67.                 // Transition to ourself to show that enter/exit is called  
  68.                 transitionTo(mS1);  
  69.                 return HANDLED;  
  70.             } else {  
  71.                 // Let parent process all other messages