实战StateMachine状态机

2,941 阅读5分钟

实战StateMachine状态机

前面我们讲解了设计模式中的状态模式,以及分析了安卓源码中的StateMachine状态机的内部实现原理其原理简而言之:就是通过状态树维持各种状态实例,各个状态实例相邻之间可以互相转换,不相邻的便通过内部状态树遍历查找。通过消息传递机制,发送消息并转换状态实例,从而实现对应状态下响应对应状态下应有的逻辑。

状态机的实现步骤

  1. 自定义StateMachine类继承StateMachine
  2. 自定义状态State继承State:重写enter、processMsg、exit
  3. 设置更新状态常量,用于状态更新
  4. addState加入父状态、子状态,setInitialState初始化状态
  5. start启动状态机stateMachine
  6. 使用状态机,发送持有更新状态常量的Msg,处理对应的pressageMessage

示例代码

  • 我们以电梯运行为例实现状态机的实现过程
  1. 状态分析:

     待机	停机
     等待选择(上楼or下楼)
     运行中
     到站
    
  2. 自定义LiftStateMachine继承StateMachine

     public class LiftStateMachine extends StateMachine {
     
         private static LiftStateMachine liftStateMachine;
         public static final int MSG_ON = 1;
         public static final int MSG_BUSY_UP = 2;
         public static final int MSG_BUSY_DOWN = 3;
         public static final int MSG_OVER = 4;
         public static final int MSG_OFF = 5;
         public static final int MSG_WAIT = 6;
     
         protected LiftStateMachine(String name) {
             super(name);
             initLiftStateMachine();
         }
     
         protected LiftStateMachine(String name, Looper looper) {
             super(name, looper);
             initLiftStateMachine();
         }
     
         protected LiftStateMachine(String name, Handler handler) {
             super(name, handler);
             initLiftStateMachine();
         }
     
         private State liftOnState = new LiftOnState();
         private State liftWaitState = new LiftWaitForUpOrDownState();
         private State liftBusyState = new LiftBusyState();
         private State liftOverState = new LiftOverState();
         private State liftOffState = new LiftOffState();
     
         private void initLiftStateMachine() {
     
             //添加状态
             addState(liftOnState);
             addState(liftWaitState);
             addState(liftBusyState);
             addState(liftOverState);
             addState(liftOffState);
     
             //调用父类方法设置初始状态
             setInitialState(liftOnState);
             //开启状态机
             start();
         }
     
         //创建状态机
         public static StateMachine makeLiftStateMachine(){
             liftStateMachine = new LiftStateMachine("LiftStateMachine");
             return liftStateMachine;
     	}
    	}
    

    上述代码中首先定义几种状态响应的常量,用于在各种State中分辨并响应对应的handleMessage消息。其次是实例出电梯的几种状态实例,并在初始化中加入状态树,之后设置原始状态,最后开启状态机进行工作。

  3. 电梯中各种状态的具体

     3.1 待机状态
     	
     	static class LiftOnState extends State {
     
             @Override
             public void enter() {
                 super.enter();
                 Log.d(TAG, "LiftOnState --> enter");
                 
                 Message message = liftStateMachine.obtainMessage();
                 message.what = MSG_WAIT;
                 liftStateMachine.sendMessage(message);
     
             }
     
             @Override
             public boolean processMessage(Message msg) {
     
                 if (msg.what == MSG_WAIT){
                     liftStateMachine.deferMessage(msg);
                     liftStateMachine.transitionTo(liftStateMachine.liftWaitState);
                 }
                 return super.processMessage(msg);
             }
     
             @Override
             public void exit() {
                 super.exit();
                 Log.d(TAG, "LiftOnState --> exit");
             }
         }
     待机状态中,在进入状态中便发送一个MSG_WAIT消息,由于当前没有切换状态实例,只有在自己的processMessage方法中处理,之后在自己的消息处理逻辑中转移消息,并切换状态实例到等待状态,此时对应电梯门前有人按下电梯按钮,需要等待电梯到达当前楼层。
     
     3.2 等待状态
    
     	static class LiftWaitForUpOrDownState extends State {
     
             @Override
             public void enter() {
                 super.enter();
                 Log.d(TAG, "LiftWaitForUpOrDownState --> enter");
             }
     
             @Override
             public boolean processMessage(Message msg) {
                 switch (msg.what){
                     case MSG_WAIT:
     
                         Log.d(TAG, "LiftWaitForUpOrDownState --> processMessage");
                         Message msg1 = liftStateMachine.obtainMessage();
                         msg1.what = MSG_BUSY_UP;
                         liftStateMachine.sendMessage(msg1);
     
                         //延迟下楼消息
                         Message message = liftStateMachine.obtainMessage();
                         message.what = MSG_BUSY_DOWN;
                         liftStateMachine.sendMessage(message);
                         liftStateMachine.transitionTo(liftStateMachine.liftBusyState);
                         break;
                 }
     
     
                 return super.processMessage(msg);
             }
     
             @Override
             public void exit() {
                 super.exit();
                 Log.d(TAG, "LiftWaitForUpOrDownState --> exit");
             }
         }
         
     等待状态逻辑也很简单,主要接收处理MSG_WAIT消息。通过切换状态,进入等待状态的enter方法,之后响应发来的MSG_WAIT消息。此时电梯到达目标楼层,开门等待人员进入并关闭,通过代码可以看出,电梯先向上运行,再向下运行,这时候电梯就要转换状态进入运行状态,此时不能再进行开门和关门等操作。
     
     3.3 电梯运行状态
    
     	static class LiftBusyState extends State {
     
             @Override
             public void enter() {
                 super.enter();
                 Log.d(TAG, "LiftBusyState --> enter");
             }
     
             @Override
             public boolean processMessage(Message msg) {
                 switch (msg.what){
                     case MSG_BUSY_UP:
                         Log.d(TAG, "LiftBusyState --> MSG_BUSY_UP");
     
                         break;
                     case MSG_BUSY_DOWN:
                         Log.d(TAG, "LiftBusyState --> MSG_BUSY_DOWN");
                         Message m = liftStateMachine.obtainMessage();
                         m.what = MSG_OVER;
                         liftStateMachine.sendMessage(m);
                         liftStateMachine.transitionTo(liftStateMachine.liftOverState);
     
                 }
     
                 return super.processMessage(msg);
             }
     
             @Override
             public void exit() {
                 super.exit();
                 Log.d(TAG, "LiftBusyState --> exit");
             }
         }
     
     人员进入电梯,电梯便运行到达指定楼层,代码中电梯先是到达高楼层,紧接着到达低楼层后,进入到达状态,这是需要请求开门后,人员才可以出入。
     
     3.4 到达状态
     
     	static class LiftOverState extends State {
     
             @Override
             public void enter() {
                 super.enter();
                 Log.d(TAG, "LiftOverState --> enter");
             }
     
             @Override
             public boolean processMessage(Message msg) {
     
                 switch (msg.what){
                     case MSG_OVER:
                         Log.d(TAG, "LiftOverState --> MSG_OVER");
                         Message message = liftStateMachine.obtainMessage();
                         message.what = MSG_OFF;
                         liftStateMachine.sendMessage(message);
                         break;
                     case MSG_OFF:
                         Log.d(TAG, "LiftOverState --> MSG_OFF");
                         Log.d(TAG, "transitionTo(liftOffState), sendMessage(MSG_OFF)");
                         Message newMsg = liftStateMachine.obtainMessage();
                         newMsg.what = MSG_OFF;
                         liftStateMachine.sendMessage(newMsg);
                         liftStateMachine.transitionTo(liftStateMachine.liftOffState);
                         break;
                 }
     
     
                 return super.processMessage(msg);
             }
           	
           	Override
             public void exit() {
                 super.exit();
                 Log.d(TAG, "LiftOverState --> exit");
             }
         }
    
     此时电梯到达指定楼层,电梯开门,人员可以安全的出入。代码中,此种状态下响应MSG_OVER消息。但是代码中再响应MSG_OVER没有切换状态,而是在自己响应自己发出的MSG_OFF的消息。稍后我们可以查看log得出此时的MSG_OFF消息是到达状态响应还是已经跳转到停机状态,由停机状态响应。
     
     3.5 停机状态
     
     	static class LiftOffState extends State {
     
             @Override
             public void enter() {
                 super.enter();
                 Log.d(TAG, "LiftOffState --> enter");
             }
     
             @Override
             public boolean processMessage(Message msg) {
                 switch (msg.what){
                     case MSG_OFF:
                         Log.d(TAG, "LiftOffState --> MSG_OFF");
                     break;
                     default:
                         break;
                 }
                 return super.processMessage(msg);
             }
     
             @Override
             public void exit() {
                 super.exit();
                 Log.d(TAG, "LiftOffState --> exit");
             }
         }
         
     电梯停机状态逻辑更是简单,对传来的MSG_OFF关机消息,简单的打印了一个log。当然可以在这里响应其他楼层的电梯等待行为,需要接收MSG_WAIT消息,并将当前状态切换为等待状态,之后电梯才能知道自己要进入运行状态到达指定楼层去接人。
    
  4. log分析总结

    通过控制台打印的log进行总结

     //电梯开机,便发送消息进入WAIT状态
     LiftState: LiftOnState --> enter
     LiftState: LiftOnState --> exit
     
     //WAIT状态下等待人员进入,之后电梯关门进入运行状态
     LiftWaitForUpOrDownState --> enter
     LiftWaitForUpOrDownState --> processMessage
     LiftWaitForUpOrDownState --> exit
     
     //电梯运行状态,先上后下,最后到达指定楼层,进入到达状态
     LiftBusyState --> enter
     LiftBusyState --> MSG_BUSY_UP
     LiftBusyState --> MSG_BUSY_DOWN
     LiftBusyState --> exit
     
     //到达指定楼层,开门等待人员出入
     LiftOverState --> enter
     LiftOverState --> MSG_OVER
     //这里可以看出,状态没有改变,MSG_OFF仍然在到达状态下响应的,需要切换到停机状态才能真正实现电梯停机
     LiftOverState --> MSG_OFF
     transitionTo(liftOffState), sendMessage(MSG_OFF)
     LiftOverState --> exit
     
     //电梯真正停机,没法响应其他的操作,此时逻辑上只能响应开即进入待机状态,无法进入等待,运行,达到等其他状态
     LiftOffState --> enter
     LiftOffState --> MSG_OFF
    

总结

  • 通过安卓源码的状态机机制,我们可以方便快捷的设计自己的状态模式模型
  • 状态模式的精髓就是各个状态下,各自响应自己能够操作的逻辑。对于越界操作,需要转移状态交予正确的状态来处理。
  • 维护状态的状态树原型也是一种跨越性的思维,其约束了各种状态间之间莫名的转换关系