android状态机statemachine详解

先说两句题外话,很感谢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][/java] view plaincopyprint?

  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][/java] view plaincopyprint?

  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][/java] view plaincopyprint?

  1. addState(mDisconnected);
  2. addState(mPending);
  3. addState(mConnected);
  4. addState(mAudioOn);
  5. setInitialState(mDisconnected);

以上两块代码,第一块是建立本状态机的整个框架,就相当于建立整个状态树,第二个是设置初始化状态。再看看HeadsetStateMachine中的静态代码块

[java][/java] view plaincopyprint?

  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][/java] view plaincopyprint?

  1. class Hsm1 extends StateMachine {
  2.     private static final String TAG = “hsm1”;
  3.     public static final int CMD_1 = 1;
  4.     public static final int CMD_2 = 2;
  5.     public static final int CMD_3 = 3;
  6.     public static final int CMD_4 = 4;
  7.     public static final int CMD_5 = 5;
  8.     public static Hsm1 makeHsm1() {
  9.         Log.d(TAG, “makeHsm1 E”);
  10.         Hsm1 sm = new Hsm1(“hsm1”);
  11.         sm.start();
  12.         Log.d(TAG, “makeHsm1 X”);
  13.         return sm;
  14.     }
  15.     Hsm1(String name) {
  16.         super(name);
  17.         Log.d(TAG, “ctor E”);
  18.         // Add states, use indentation to show hierarchy
  19.         addState(mP1);
  20.             addState(mS1, mP1);
  21.             addState(mS2, mP1);
  22.         addState(mP2);
  23.         // Set the initial state
  24.         setInitialState(mS1);
  25.         Log.d(TAG, “ctor X”);
  26.     }
  27.     class P1 extends State {
  28.         @Override public void enter() {
  29.             Log.d(TAG, “mP1.enter”);
  30.         }
  31.         @Override public boolean processMessage(Message message) {
  32.             boolean retVal;
  33.             Log.d(TAG, “mP1.processMessage what=” + message.what);
  34.             switch(message.what) {
  35.             case CMD_2:
  36.                 // CMD_2 will arrive in mS2 before CMD_3
  37.                 sendMessage(obtainMessage(CMD_3));
  38.                 deferMessage(message);
  39.                 transitionTo(mS2);
  40.                 retVal = HANDLED;
  41.                 break;
  42.             default:
  43.                 // Any message we don’t understand in this state invokes unhandledMessage
  44.                 retVal = NOT_HANDLED;
  45.                 break;
  46.             }
  47.             return retVal;
  48.         }
  49.         @Override public void exit() {
  50.             Log.d(TAG, “mP1.exit”);
  51.         }
  52.     }
  53.     class S1 extends State {
  54.         @Override public void enter() {
  55.             Log.d(TAG, “mS1.enter”);
  56.         }
  57.         @Override public boolean processMessage(Message message) {
  58.             Log.d(TAG, “S1.processMessage what=” + message.what);
  59.             if (message.what == CMD_1) {
  60.                 // Transition to ourself to show that enter/exit is called
  61.                 transitionTo(mS1);
  62.                 return HANDLED;
  63.             } else {
  64.                 // Let parent process all other messages
  65.                 return NOT_HANDLED;
  66.             }
  67.         }
  68.         @Override public void exit() {
  69.             Log.d(TAG, “mS1.exit”);
  70.         }
  71.     }
  72.     class S2 extends State {
  73.         @Override public void enter() {
  74.             Log.d(TAG, “mS2.enter”);
  75.         }
  76.         @Override public boolean processMessage(Message message) {
  77.             boolean retVal;
  78.             Log.d(TAG, “mS2.processMessage what=” + message.what);
  79.             switch(message.what) {
  80.             case(CMD_2):
  81.                 sendMessage(obtainMessage(CMD_4));
  82.                 retVal = HANDLED;
  83.                 break;
  84.             case(CMD_3):
  85.                 deferMessage(message);
  86.                 transitionTo(mP2);
  87.                 retVal = HANDLED;
  88.                 break;
  89.             default:
  90.                 retVal = NOT_HANDLED;
  91.                 break;
  92.             }
  93.             return retVal;
  94.         }
  95.         @Override public void exit() {
  96.             Log.d(TAG, “mS2.exit”);
  97.         }
  98.     }
  99.     class P2 extends State {
  100.         @Override public void enter() {
  101.             Log.d(TAG, “mP2.enter”);
  102.             sendMessage(obtainMessage(CMD_5));
  103.         }
  104.         @Override public boolean processMessage(Message message) {
  105.             Log.d(TAG, “P2.processMessage what=” + message.what);
  106.             switch(message.what) {
  107.             case(CMD_3):
  108.                 break;
  109.             case(CMD_4):
  110.                 break;
  111.             case(CMD_5):
  112.                 transitionToHaltingState();
  113.                 break;
  114.             }
  115.             return HANDLED;
  116.         }
  117.         @Override public void exit() {
  118.             Log.d(TAG, “mP2.exit”);
  119.         }
  120.     }
  121.     @Override
  122.     void onHalting() {
  123.         Log.d(TAG, “halting”);
  124.         synchronized (this) {
  125.             this.notifyAll();
  126.         }
  127.     }
  128.     P1 mP1 = new P1();
  129.     S1 mS1 = new S1();
  130.     S2 mS2 = new S2();
  131.     P2 mP2 = new P2();
  132. }
  133. </code>
  134.  * <p>If this is executed by sending two messages CMD_1 and CMD_2
  135.  * (Note the synchronize is only needed because we use hsm.wait())</p>
  136. <code>
  137. Hsm1 hsm = makeHsm1();
  138. synchronize(hsm) {
  139.      hsm.sendMessage(obtainMessage(hsm.CMD_1));
  140.      hsm.sendMessage(obtainMessage(hsm.CMD_2));
  141.      try {
  142.           // wait for the messages to be handled
  143.           hsm.wait();
  144.      } catch (InterruptedException e) {
  145.           Log.e(TAG, “exception while waiting ” + e.getMessage());
  146.      }
  147. }
  148. 输出如下:
  149. D/hsm1    ( 1999): makeHsm1 E
  150. D/hsm1    ( 1999): ctor E
  151. D/hsm1    ( 1999): ctor X
  152. D/hsm1    ( 1999): mP1.enter
  153. D/hsm1    ( 1999): mS1.enter
  154. D/hsm1    ( 1999): makeHsm1 X
  155. D/hsm1    ( 1999): mS1.processMessage what=1
  156. D/hsm1    ( 1999): mS1.exit
  157. D/hsm1    ( 1999): mS1.enter
  158. D/hsm1    ( 1999): mS1.processMessage what=2
  159. D/hsm1    ( 1999): mP1.processMessage what=2
  160. D/hsm1    ( 1999): mS1.exit
  161. D/hsm1    ( 1999): mS2.enter
  162. D/hsm1    ( 1999): mS2.processMessage what=2
  163. D/hsm1    ( 1999): mS2.processMessage what=3
  164. D/hsm1    ( 1999): mS2.exit
  165. D/hsm1    ( 1999): mP1.exit
  166. D/hsm1    ( 1999): mP2.enter
  167. D/hsm1    ( 1999): mP2.processMessage what=3
  168. D/hsm1    ( 1999): mP2.processMessage what=4
  169. D/hsm1    ( 1999): mP2.processMessage what=5
  170. D/hsm1    ( 1999): mP2.exit
  171. D/hsm1    ( 1999): halting

标签