在Android系统中,经常使用状态机来处理不同状态下的行为动作。状态机是将对象的状态与行为封装在一起;可以解决庞大的分支语句带来程序阅读性差和不便于进行扩展问题,使整个结构变得更加清晰明了,降低程序管理的复杂性提高灵活度。Android系统的StateMachine机制是一个State模式的应用,StateMachine是一个分层处理消息的状态机,并且是能够有分层排列状态。

  构造状态机

  StateMachine的构造函数都是protected类型,不能实例化,都是由其子类进行初始化操作。StateMachine有两个重载的构造函数,一个是通过指定消息循环对象来构造状态机

  java代码

1. protected StateMachine(String name, Looper looper) {
2. 3.   initStateMachine(name, looper);
4.   }

复制代码

  另一个则是直接启动一个消息循环线程来构造一个状态机

  java代码

1. protected StateMachine(String name) {
2. 3.   //启动一个消息循环线程
4.   mSmThread = new HandlerThread(name);
5.   mSmThread.start();
6.   Looper looper = mSmThread.getLooper();
7.   initStateMachine(name, looper);
8.   }

复制代码

  这两种构造函数都会通过initStateMachine函数来初始化该状态机

  java代码

1. private void initStateMachine(String name, Looper looper) {
2. 3.   mName = name;
4.   mSmHandler = new SmHandler(looper, this);
5.   }

复制代码

  初始化过程比较简单,就是将状态机名称保存到成员变量mName中,同时创建SmHandler对象,SmHandler是一个Handle对象,用于派发消息。

  

  状态机中的每个状态使用State来封装,对于每个状态的信息又采用StateInfo来描述

  StateMachine三个内部类:

  1.ProcessedMessageInfo:保存已处理消息的信息;

  2.ProcessedMessages:存储StateMachine最近处理的一些消息,需要保存最近处理的消息条数默认20,可以用户自己设定最大数目;

  3.SmHandle是消息处理派发和状态控制切换的核心,运行在单独的线程上;

  ProcessedMessages用于保存已处理过的消息,mStateStack和mTempStateStack是一个数组栈,用于保存状态机中的链式状态关系。

  

  mStateInfo定义为一个Hash链表,用于保存添加的所有状态。mInitialState保存初始状态,mDestState保存切换的目的状态。

  建立树形层次结构状态机

  在构造完一个状态机前需要向状态机中添加各种状态,StateMachine提供了addState函数来添加状态

  java代码

1. protected final void addState(State state, State parent) {
2. 3.   mSmHandler.addState(state, parent);
4.   }

复制代码

  直接通过mSmHandler来完成状态添加过程

  java代码

1. private final StateInfo addState(State state, State parent) {
2. 3.   if (mDbg) {
4.   Log.d(TAG, "addStateInternal: E state=" + state.getName()
5.   + ",parent=" + ((parent == null) ? "" : parent.getName()));
6.   }
7.   StateInfo parentStateInfo = null;
8.   if (parent != null) {
9.   parentStateInfo = mStateInfo.get(parent);
10.   if (parentStateInfo == null) {
11.   // Recursively add our parent as it's not been added yet.
12.   parentStateInfo = addState(parent, null);
13.   }
14.   }
15.   StateInfo stateInfo = mStateInfo.get(state);
16.   if (stateInfo == null) {
17.   stateInfo = new StateInfo();
18.   mStateInfo.put(state, stateInfo);
19.   }
20.   // Validate that we aren't adding the same state in two different 
21. hierarchies.
22.   if ((stateInfo.parentStateInfo != null) &&
23.   (stateInfo.parentStateInfo != parentStateInfo)) {
24.   throw new RuntimeException("state already added");
25.   }
26.   stateInfo.state = state;
27.   stateInfo.parentStateInfo = parentStateInfo;
28.   stateInfo.active = false;
29.   if (mDbg) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo);
30.   return stateInfo;
31.   }

复制代码

  状态添加过程其实就是为每个State创建相应的StateInfo对象,通过该对象来建立各个状态之间的关系,并且一个State-StateInfo键值对的方式保存到mStateInfo Hash表中。StateInfo就是包装State组成一个Node,建立State的父子关系;mStateInfo =new HashMap();用来保存State Machine中的所有State,可以按照树形层次结构组织状态机中的所有状态

  接下来介绍构造以下树形状态机的过程:

  java代码

1. sm.addState(S0,null);
2. 3.   sm.addState(S1,S0);
4.   sm.addState(S2,S0);
5.   sm.addState(S3,S1);
6.   sm.addState(S4,S1);
7.   sm.addState(S5,S2);
8.   sm.addState(S6,S2);
9.   sm.addState(S7,S2);
10.   setInitialState(S4); //设置初始状态

复制代码

  1.添加根节点状态过程

  对于根节点状态的加入sm.addState(S0,null)代码执行如下:

  java代码

1. private final StateInfo addState(S0,null)
2. 3.   {
4.   StateInfo parentStateInfo = null;
5.   //状态S0还未加入状态机中
6.   StateInfo stateInfo = mStateInfo.get(state);
7.   if (stateInfo == null) {
8.   //创建State详细信息对象
9.   stateInfo = new StateInfo();
10.   //将S0加入状态机中
11.   mStateInfo.put(state, stateInfo);
12.   }
13.   //设置状态S0的状态信息
14.   stateInfo.state = state;
15.   //S0的父节点为null
16.   stateInfo.parentStateInfo = parentStateInfo;
17.   stateInfo.active = false;
18.   return stateInfo;
19.   }

复制代码

  2.添加树枝节点状态过程

  对于子节点S1状态的加入过程如下sm.addState(S1,S0):

  java代码

1. private final StateInfo addState(State state, State parent)
2. 3.   {
4.   StateInfo parentStateInfo = null;
5.   //状态S0在前面已经加入状态机中了
6.   if (parent != null) {
7.   //获取S0详细信息 StateInfo
8.   parentStateInfo = mStateInfo.get(parent);
9.   //因为S0已经存在于状态机中,因此这里不为空
10.   if (parentStateInfo == null) {
11.   //当前状态父状态未加入到StateMachine中,递归先加入其父State
12.   parentStateInfo = addState(parent, null);
13.   }
14.   }
15.   //从状态机中得到S1的状态信息,由于S1还为加入状态机,因此得到的结果为空
16.   StateInfo stateInfo = mStateInfo.get(state);
17.   if (stateInfo == null) {
18.   //创建State详细信息对象
19.   stateInfo = new StateInfo();
20.   //将S1加入状态机中
21.   mStateInfo.put(state, stateInfo);
22.   }
23.   //S1的状态信息刚创建,还没有为其设置父节点,因此其父节点为空
24.   if ((stateInfo.parentStateInfo != null) &&
25.   (stateInfo.parentStateInfo != parentStateInfo)) {
26.   throw new RuntimeException("state already added");
27.   }
28.   //设置状态S1的状态信息
29.   stateInfo.state = state;
30.   //S1的父节点为S0
31.   stateInfo.parentStateInfo = parentStateInfo;
32.   stateInfo.active = false;
33.   return stateInfo;
34.   }

复制代码

  对于其他树枝节点的添加过程类似,这里不在介绍,最后保存在mStateInfo表中的所有状态之间就建立了以下树形关系

  启动状态机

  当向状态机中添加完所有状态时,通过函数start函数来启动状态机

  java代码

1. public void start() {
2. 3.   // mSmHandler can be null if the state machine has quit.
4.   if (mSmHandler == null) return;
5.   /** Send the complete construction message */
6.   mSmHandler.completeConstruction();
7.   }

复制代码

  调用mSmHandler的completeConstruction函数来完成状态机的构造完成处理

  java代码

1. private final void completeConstruction() {
2. 3.   if (mDbg) Log.d(TAG, "completeConstruction: E");
4.   //查找状态树的深度
5.   int maxDepth = 0;
6.   for (StateInfo si : mStateInfo.values()) {
7.   int depth = 0;
8.   //根据父子关系计算树枝层次数
9.   for (StateInfo i = si; i != null; depth++) {
10.   i = i.parentStateInfo;
11.   }
12.   if (maxDepth < depth) {
13.   maxDepth = depth;
14.   }
15.   }
16.   if (mDbg) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth);
17.   //创建mStateStack,mTempStateStack状态栈
18.   mStateStack = new StateInfo[maxDepth];
19.   mTempStateStack = new StateInfo[maxDepth];
20.   //设置状态堆栈
21.   setupInitialStateStack();
22.   /** Sending SM_INIT_CMD message to invoke enter methods asynchronously 
23. */
24.   sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
25.   if (mDbg) Log.d(TAG, "completeConstruction: X");
26.   }

复制代码

  计算状态树的最大深度方法:

  1.遍历状态树中的所有节点;

  2.以每一个节点为起始点,根据节点父子关系查找到根节点;

  3.累计起始节点到根节点之间的节点个数;查找最大的节点个数。

  根据查找到的树的最大节点个数来创建两个状态堆栈,并调用函数setupInitialStateStack来填充该堆栈

  java代码

1. private final void setupInitialStateStack() {
2. 3.   //在mStateInfo中取得初始状态mInitialState对应的StateInfo
4.   StateInfo curStateInfo = mStateInfo.get(mInitialState);
5.   //从初始状态mInitialState开始根据父子关系填充mTempStateStack堆栈
6.   for (mTempStateStackCount = 0; curStateInfo != null; 
7. mTempStateStackCount++) {
8.   mTempStateStack[mTempStateStackCount] = curStateInfo;
9.   curStateInfo = curStateInfo.parentStateInfo;
10.   }
11.   // Empty the StateStack
12.   mStateStackTopIndex = -1;
13.   //将mTempStateStack中的状态按反序方式移动到mStateStack栈中
14.   moveTempStateStackToStateStack();
15.   }

复制代码

  

  从上图可以看出,当初始状态为S4时,保存到mTempStateStack的节点为:

 

mTempStateStack={S4,S1,S0} 
  mTempStateStackCount = 3; 
  mStateStackTopIndex = -1;

  然后调用函数moveTempStateStackToStateStack将节点以反序方式保存到mStateStack中

  java代码

1. private final int moveTempStateStackToStateStack() {
2. 3.   //startingIndex= 0
4.   int startingIndex = mStateStackTopIndex + 1;
5.   int i = mTempStateStackCount - 1;
6.   int j = startingIndex;
7.   while (i >= 0) {
8.   if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
9.   mStateStack[j] = mTempStateStack[i];
10.   j += 1;
11.   i -= 1;
12.   }
13.   mStateStackTopIndex = j - 1;
14.   return startingIndex;
15.   }

复制代码

mStateStack={S0,S1,S4} 
  mStateStackTopIndex = 2

  初始化完状态栈后,SmHandler将向消息循环中发送一个SM_INIT_CMD消息

  java代码

1. sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, 
2. mSmHandlerObj))

复制代码

  该消息对应的处理如下:

  java代码

1. else if (!mIsConstructionCompleted &&(mMsg.what == 
2. SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
3. 4.   mIsConstructionCompleted = true;
5.   invokeEnterMethods(0);
6.   }
7.   performTransitions();

复制代码

  消息处理过程首先调用invokeEnterMethods函数将mStateStack栈中的所有状态设置为激活状态,同时调用每一个状态的enter()函数

  java代码

1. private final void invokeEnterMethods(int 
2. stateStackEnteringIndex) {
3. 4.   for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) 
5. {
6.   if (mDbg) Log.d(TAG, "invokeEnterMethods: " + 
7. mStateStack[i].state.getName());
8.   mStateStack[i].state.enter();
9.   mStateStack[i].active = true;
10.   }
11.   }

复制代码

  最后调用performTransitions函数来切换状态,同时设置mIsConstructionCompleted为true,表示状态机已经启动完成,SmHandler在以后的消息处理过程中就不在重新启动状态机了。

  状态切换

  SmHandler在处理每个消息时都会调用performTransitions来检查状态切换

  java代码

1. private synchronized void performTransitions() {
2. 3.   while (mDestState != null){
4.   //当前状态切换了 存在于mStateStack中的State需要改变
5.   //仍然按照链式父子关系来存储
6.   //先从当前状态S3找到 最近的被激活的parent状态S0
7.   //未被激活的全部保存起来(S3,S1) 返回S0
8.   StateInfo commonStateInfo = 
9. setupTempStateStackWithStatesToEnter(destState);
10.   //将mStateStack中 不属于当前状态(S3),
11.   //关系链上的State(S5,S2)退出(执行exit方法)
12.   invokeExitMethods(commonStateInfo);
13.   //将S3关系链 加入到栈中(S3,S1)
14.   int stateStackEnteringIndex = moveTempStateStackToStateStack();
15.   //将新加入到mStateStack中 未被激活的State激活(S3,S1)
16.   invokeEnterMethods(stateStackEnteringIndex);
17.   //将延迟的消息移动到消息队列的前面,以便快速得到处理
18.   moveDeferredMessageAtFrontOfQueue();
19.   }
20.   }

复制代码

  首先介绍一下状态切换的思路

  以上图中,初始状态为S4,现在目标状态mDestState被设置为S7。前面介绍了保存在mStateStack数组中的节点为:

mStateStack={S0,S1,S4} 
  mStateStackTopIndex = 2

  这是以初始状态节点为起点遍历节点树得到的节点链表。

  现在要切换到S7状态节点,则以S7为起始节点,同样遍历状态节点树,查找未激活的所有节点,并保存到mTempStateStack数组中

mTempStateStack={S7,S2,S0} 
  mTempStateStackCount = 3

  接着调用mStateStack中除S0节点外的其他所有节点的exit函数,并且将每个状态节点设置为未激活状态,因此S4,S1被设置为未激活状态;将切换后的状态节点链表mTempStateStack移动到mStateStack,

 

mStateStack={S0,S2,S7} 
  mStateStackTopIndex = 2

  并调用节点S2,S7的enter函数,同时设置为激活状态。

  理解了整个状态切换过程后,就能更好地理解代码,首先根据目标状态建立状态节点链路表

  java代码

1. private final StateInfo 
2. setupTempStateStackWithStatesToEnter(State destState) {
3. 4.   mTempStateStackCount = 0;
5.   StateInfo curStateInfo = mStateInfo.get(destState);
6.   do {
7.   mTempStateStack[mTempStateStackCount++] = curStateInfo;
8.   if (curStateInfo != null) {
9.   curStateInfo = curStateInfo.parentStateInfo;
10.   }
11.   } while ((curStateInfo != null) && !curStateInfo.active);
12.   return curStateInfo;
13.   }

复制代码

  然后弹出mStateStack中保存的原始状态

  java代码

1. private final void invokeExitMethods(StateInfo 
2. commonStateInfo) {
3. 4.   while ((mStateStackTopIndex >= 0) &&
5.   (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
6.   State curState = mStateStack[mStateStackTopIndex].state;
7.   if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
8.   curState.exit();
9.   mStateStack[mStateStackTopIndex].active = false;
10.   mStateStackTopIndex -= 1;
11.   }
12.   }

复制代码

  将新建立的状态节点链表保存到mStateStack栈中

  java代码

1. private final int moveTempStateStackToStateStack() {
2. 3.   //startingIndex= 0
4.   int startingIndex = mStateStackTopIndex + 1;
5.   int i = mTempStateStackCount - 1;
6.   int j = startingIndex;
7.   while (i >= 0) {
8.   if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
9.   mStateStack[j] = mTempStateStack[i];
10.   j += 1;
11.   i -= 1;
12.   }
13.   mStateStackTopIndex = j - 1;
14.   return startingIndex;
15.   }

复制代码

  初始化入栈的所有新状态,并设置为激活状态

  java代码

1. private final void invokeEnterMethods(int 
2. stateStackEnteringIndex) {
3. 4.   for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) 
5. {
6.   if (mDbg) Log.d(TAG, "invokeEnterMethods: " + 
7. mStateStack[i].state.getName());
8.   mStateStack[i].state.enter();
9.   mStateStack[i].active = true;
10.   }
11.   }

复制代码

  如何设置目标状态呢?StateMachine提供了transitionTo函数来切换状态

  java代码

1. protected final void transitionTo(IState destState) {
2. 3.   mSmHandler.transitionTo(destState);
4.   }

复制代码

  该函数直接调用SmHandler的transitionTo函数来实现,SmHandler的transitionTo函数定义如下:

  java代码

1. private final void transitionTo(IState destState) {
2. 3.   mDestState = (State) destState;
4.   if (mDbg) Log.d(TAG, "transitionTo: destState=" + 
5. mDestState.getName());
6.   }

复制代码

  这里只是简单地设置了mDestState变量,并未真正更新状态栈mStateStack,在前面介绍了SmHandler在每次处理消息时都会自动更新一次mStateStack,无论mDestState变量值是否改变。由此可知目标状态的设置与状态栈的更新是异步的。

  消息处理

  StateMachine处理的核心就是SmHandler,就是一个Handler,运行在单独线程中。Handler是用来异步处理派发消息,这里使用Handler管理各个状态,派发消息处理到各个状态中去执行。StateMachine提供了多个消息发送接口,通过这些接口函数可以将消息发送到SmHandler中。

  java代码

1. public final void sendMessage(int what) {
2. 3.   // mSmHandler can be null if the state machine has quit.
4.   if (mSmHandler == null) return;
5.   mSmHandler.sendMessage(obtainMessage(what));
6.   }
7.   SmHandler将处理通过SmHandler发送的消息,处理过程如下:
8.   java代码public final void handleMessage(Message msg) {
9. 10.   /** Save the current message */
11.   mMsg = msg;
12.   if (mIsConstructionCompleted) {
13.   //派发当前消息到state中去处理
14.   processMsg(msg);
15.   } else if (!mIsConstructionCompleted &&
16.   (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
17.   /** Initial one time path. */
18.   mIsConstructionCompleted = true;
19.   invokeEnterMethods(0);
20.   } else {
21.   throw new RuntimeException("StateMachine.handleMessage: " +
22.   "The start method not called, received msg: " + msg);
23.   }
24.   //消息处理完毕更新mStateStack
25.   performTransitions();
26.   }

复制代码

  变量mIsConstructionCompleted在状态机启动完成后被设置为true,因此这里将调用processMsg函数来完成消息处理。

  java代码

1. private final void processMsg(Message msg) {
2. 3.   StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
4.   //如果当前状态未处理该消息
5.   while (!curStateInfo.state.processMessage(msg)) {
6.   //将消息传给当前状态的父节点处理
7.   curStateInfo = curStateInfo.parentStateInfo;
8.   if (curStateInfo == null) {
9.   //当前状态无父几点,则丢弃该消息
10.   mSm.unhandledMessage(msg);
11.   if (isQuit(msg)) {
12.   transitionTo(mQuittingState);
13.   }
14.   break;
15.   }
16.   }
17.   //记录处理过的消息
18.   if (mSm.recordProcessedMessage(msg)) {
19.   if (curStateInfo != null) {
20.   State orgState = mStateStack[mStateStackTopIndex].state;
21.   mProcessedMessages.add(msg, mSm.getMessageInfo(msg), 
22. curStateInfo.state,orgState);
23.   } else {
24.   mProcessedMessages.add(msg, mSm.getMessageInfo(msg), null, null);
25.   }
26.   }
27.   }

复制代码

  消息处理过程是从mStateStack栈顶派发到栈底,直到该消息被处理!