HSM 即 Hierarchical State Machine,层次状态机,分层状态机。这个技术被广泛应用于游戏、AI 这类逻辑控制系统。
介绍 HSM 之前,要先对 State 模式有个基本的了解,因为State模式是它的原始思想。
首先,在State模式中,以下几个概念你需要知道:
- Context:保存管理 State 对象实例,并且让 State 对象执行的类,它可以理解为 State 实例所处的环境。
- State:代表每一个特定的状态。一个状态就是一个 State 的子类,它有处理 Message 的方法。
- Message:是驱动整个程序运行的消息,通过向 Context 发送 Message,对应 State 可以执行相应的操作来进行处理。
以上三个是 State 状态模式中最核心的概念,状态模式是以消息机制为核心的一种设计模式。以下实现一个简单的饮料机来让大家了解/回顾一下State模式。
饮料机的需求很简单。我们的饮料机平时处于空闲状态(FreeState),它无所事事,没有人对它进行操作。人可以对它做出以下操作:
- 投入1块钱硬币使之进入有钱状态(HasMoneyState)
- 摇动退币手柄让饮料机找钱,如果是有钱状态就会因此进入空闲状态,本身是空闲状态什么都不会做,人也可以摇动退币柄。
- 可以按下<购买>按钮,如果你在有钱状态,并且钱数够了(对了,饮料2块钱一瓶),那么ok,饮料机得花时间把饮料给你吐出来,此时是GivingState,你在 GivingState 状态什么都不能做,只能等待,等2s后饮料出来了,你才能够进行操作。当然了,饮料出来后饮料机会进入其他状态,如果正好钱用光了,那么饮料机进入空闲状态;如果还有钱,那么饮料机回到有钱状态。
下面稍微总结一下我们刚才的问题:
有以下几种状态:
- FreeState:此时饮料机正无所事事。
- HasMoneyState:饮料机中已经投进了钱。
- GivingState:饮料机正在处理将饮料抛出前的流程。
有以下一些操作:
- CMD_ADDMONEY:把1块钱放进机器。
- CMD_RETURNMONEY:摇动退币柄退币。
- CMD_BUY:点击<购买>按钮。
接下来,我们需要画一个状态切换图来将状态的切换过程表现出来:
有了这个状态切换图以后,我们就可以开始动手编写程序了。我们要创建一个 DrinkMachine 类,该类即是 State 模式的 Context。DrinkMachine 类有一些公共接口:addMoney(),returnMoney(),clickBuy(),供其它类进行掉用。DrinkMachine 类中有一个int mRestMoney 表示饮料机中的钱数,有一个 State mCurrentState 表示当前的状态。Ok,一起来实现下该类,注释都写在了程序里:
// 1. 先创建一个IState接口,这个接口定义了所有 State 的方法 processMessage(int message):
public interface IState {
public void processMessage(int message);
}
//2.创建一个 State 类,这个类实现了 IState 接口,所有的 Concrete State 都要继承该类并且重写 processMessage(int message) 方法:
// 当然,我们也可以不用创建该State类,直接让 concrete State 实现 IState 接口。那也可以,但是我这么做的目的是为了和底下要写的HSM 统一起来,这样容易一一对应,比照起来少些思维上的转换成本。
public class DrinkMachine {
private int mRestMoney = 0; //这表示饮料机中还有多少钱
private State mCurrentState = null; //记录当前饮料机所处的状态
//所有的状态实例
private State freestate;
private State hasmoneystate;
private State givingstate;
//===========一下是一批Message定义==============
static final int CMD_BASE = 0;
static final int CMD_ADDMONEY = CMD_BASE + 1;
static final int CMD_RETURNMONEY = CMD_BASE + 2;
static final int CMD_BUY = CMD_BASE + 3;
static final int CMD_GIVEDRINK = CMD_BASE + 4; //该命令让饮料机出货
//===========以下是一批状态类定义===============
//饮料机无所事事的状态
class FreeState extends State {
@Override
public void processMessage(int message) {
// TODO Auto-generated method stub
//super.processMessage(message);
switch (message) {
case CMD_ADDMONEY: {
//把机器中的钱数给加上
mRestMoney++;
//切换到有钱状态
mCurrentState = hasmoneystate;
break;
}
case CMD_RETURNMONEY: {
System.out.println("别折腾,没用的,不会掉钱下来的");
break;
}
case CMD_BUY: {
System.out.println("不会掉饮料下来的,先投钱吧");
break;
}
default: {
break;
}
}
log();
}
}
//饮料机中有钱的状态
class HasMoneyState extends State {
@Override
public void processMessage(int message) {
switch (message) {
case CMD_ADDMONEY: {
//除了变得更加有钱以外,没有其他的了
mRestMoney++;
break;
}
case CMD_RETURNMONEY: {
//退钱,并且回到空闲状态
mRestMoney = 0;
mCurrentState = freestate;
break;
}
case CMD_BUY: {
if (mRestMoney >= 2) {
//一瓶饮料两块钱,够了,可以进入出货状态了
mRestMoney -= 2;
mCurrentState = givingstate;
//发出出货命令
mCurrentState.processMessage(CMD_GIVEDRINK);
break;
}
}
default: {
break;
}
}
log();
}
}
//饮料机正在出货的状态
class GivingState extends State {
@Override
public void processMessage(int message) {
switch (message) {
case CMD_GIVEDRINK: {
try {
//机器在2秒钟里什么都不做,专心处理出货动作
Thread.sleep(2000);
//货已经出了,根据剩余的钱来看看切换到什么状态吧
if (mRestMoney > 0) {
//还有钱
mCurrentState = hasmoneystate;
} else {
//已没钱
mCurrentState = freestate;
}
break;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
default: {
break;
}
}
log();
}
}
//构造器中完成state对象创建以及初始化当前状态的操作
public DrinkMachine() {
//分别创建每个State的实例
freestate = new FreeState();
hasmoneystate = new HasMoneyState();
givingstate = new GivingState();
//设置初始状态
mCurrentState = freestate;
}
//开放方法接口,表示投入1块钱
public void addMoney() {
//让当前状态对象处理CMD_ADDMONEY消息
System.out.println("用户投了1块钱");
mCurrentState.processMessage(CMD_ADDMONEY);
}
//开放方法接口,表示摇晃退币手柄
public void returnMoney() {
//让当前状态对象处理CMD_RETURNMONEY消息
System.out.println("用户摇了退币手柄");
mCurrentState.processMessage(CMD_RETURNMONEY);
}
//开放方法接口,表示按下<购买>按钮
public void clickBuy() {
//让当前状态对象处理CMD_BUY消息
System.out.println("用户按了<购买>按钮");
mCurrentState.processMessage(CMD_BUY);
}
//输出当前状态以及剩余钱树的log方法
private void log() {
if (mCurrentState == null) {
return;
}
String statename = "";
if (mCurrentState instanceof FreeState) {
statename = "FreeState";
} else if (mCurrentState instanceof HasMoneyState) {
statename = "HasMoneyState";
} else if (mCurrentState instanceof GivingState) {
statename = "GivingState";
}
System.out.print("处理完Message以后,状态:" + statename);
System.out.println(",余额:" + mRestMoney + "\n");
}
}
4.Context创建好了,那么我们写个test类来实验一下吧:
public class Test {
public static void main(String[] args) {
DrinkMachine dm=new DrinkMachine();
dm.addMoney();
dm.addMoney();
dm.clickBuy();
dm.clickBuy();
}
}
看输出,即可。你也可以对 DrinkMachine 发各种各样的命令,State 模式都可以很稳健地工作。
OK,以上就是一个State模式的典型应用。要解决的问题是复杂的,但是通过利用State模式,非常简单地解决了问题,整个过程是非常清晰的:
- 先写一个抽象的IState接口,为它有一个方法:boolean processMessage(int Message)。
- 定义一票 Message,它们就是会触发 State 处理事件的一些消息常量。
- 画出状态切换图,确定每个 State 会在各种 Message 的推动下,做些什么处理,并且处理后会切换到什么状态。
- 对照 Step3 的状态切换图,依次在 Context 中实现每一个 Concrete State,它们都是实现了 IState 接口的类,或是继承自实现了 IState接口的 State 类。
- 在 Context 中创建每一个状态的实例,然后在发送消息前先设置好 Context 的初始状态(Initial State)。
- 准备好以上之后,只需要向当前状态(一个记录当前状态的 IState 实例)不断地发送消息就行了,State 模式会帮你包办一切。
我们想象一下如果没有 State 模式,那么我们实现饮料机会是多么痛苦的一个过程。当我们对饮料机按下退币手柄,我们需要去判断是否正在出货,然后再判断是否有零钱。如果我们按下购买按钮,我们也需要判断是否在出货,钱是否够,我们不得不维护更多多余繁杂的变量例如记录当前是否正在出货的 mInGivingProcess 之类的 bool 变量,大量的 if/else 让人容易犯错。但是,有了状态模式以后就无需这样,直接让当前状态去处理 Message 就行了,它知道接下来该干嘛。而如果我们需要新加入一些功能,只需添加新状态,让所有状态添加处理新 Message 代码,并且稍微修正一下状态切换即可。State 状态是低耦合易拓展的。
State 模式简单强大,但是也有其不足之处:当它处理大量的 State 时,实现状态切换的过程将变得非常痛苦。假设有 n 个状态,我们可能不得不实现接近 nn 个状态切换的代码,这是很不现实的,显然这会花费大量的精力。于是我们引入 HSM 这个可以处理这种问题的新型 State 模式。HSM 的得力之处在于我们能够通过归纳状态之间的层次结构建立一个状态树,然后通过在树的节点上直接进行逐步预先设立的 exit 和 enter 方法来实现状态进入,退出时的行为,然后我们想让状态切换时,程序会自动根据状态树上的节点关系,自动地进行状态切换,也就是说,我们只需要为每个状态实现 exit 和 enter,然后指定每个 State 对某一 Message 是否能处理,接下来切换过程也就自动由状态机替我们完成了,这样我们的工作量就是 3n 个方法的设计的设计量,而且每个方法都比之前的 n*n 个方法中的要简单(理论上),因为它无需考虑其他节点的状态,只需考虑自己就行了。现在我说这些还是空中楼阁,我们需要通过接下来的 demo 以及源码分析等来理解我现在说的这段结论。
假设,我们需要实现一个
它需要拥有如下的状态:
- DefaultState:只有一个能跑的履带的下盘的状态。
- BaseSetupStartingState:基座安装中。
- BaseSetupFinishedState:基座安装完毕。
- BarrelSetupStartingState:炮筒安装中,必须要在基座安装完毕才能进入该状态。
- BarrelSetupFinishedState:炮筒安装完毕,必须要在基座安装完毕才能进入该状态。
- BigBarrelSetupStartingState:大口径炮筒安装中,必须要在基座安装完毕才能进入该状态。
- BigBarrelSetupFinishedState:大口径炮筒安装完毕,必须要在基座安装完毕才能进入该状态。
- AddMissileSuccessedState:成功装填导弹,必须要在炮筒安装完毕才能进入该状态。
- AddRocketSuccessedState:成功装填火箭,必须要在大口径炮筒安装完毕才能进入该状态。
我们再定义了一票Message,让Tank来处理:
- CMD_ATTACK:攻击。
- CMD_SETUP_BASE:安装基座。
- CMD_REMOVE_BASE:卸载基座。
- CMD_SETUP_BARREL:安装炮筒。
- CMD_REMOVE_BARREL:卸载炮筒。
- CMD_SETUP_BIG_BARREL:安装大口径炮筒。
- CMD_REMOVE_BIG_BARREL:卸载大口径炮筒。
- CMD_ADD_MISSILE:装填导弹。
- CMD_LAUNCH_MISSILE:发射导弹。
- CMD_REMOVE_MISSILE:移除导弹。
- CMD_ADD_ROCKET:装填火箭。
- CMD_LAUNCH_ROCKET:发射火箭。
- CMD_REMOVE_ROCKET:移除火箭。
- CMD_BASE_SETUP_SUCCESSED:该条命令一般由其他类定义,并且发送Message过来,通知TankStateMachine,基座已经准备就绪。
- CMD_BARREL_SETUP_SUCCESSED:同上,由实际安装炮筒的类发送给TankStateMachine,炮筒安装完毕。
- CMD_BIGBARREL_SETUP_SUCCESSED:同上,由实际安装炮筒的类发送给TankStateMachine,大口径炮筒安装完毕。
有了以上 State 以及 Message 以后,我们先考虑使用<传统 State 模式>来解决用户需求。
这些状态转换流程图如下:
这非常不科学,可以看到我们需要在不同状态切换的过程中写很多重复的代码,并且状态之间的依赖关系需要在代码中体现出来,这使得代码的可拓展性非常的差,这违背了设计模式的基本原则:易于拓展。
这时候我们就需要利用 HSM 来解决该问题了(我们底下剖析 HSM 所使用的代码实现,是 Android framework 中的源码,路径是: /frameworks/base/core/java/com/android/internal/util/StateMachine.java ,当然 HSM 还有其他的源码实现,但是其思想是类似的)。层次状态机,从名字中就可以看出它即是有层次的状态机。其中,层次是靠树这种数据结构来体现并记录的。层次状态机的基本思想同状态模式大致上是一致的,其中我们实现的 StateMachine 子类即是 Context,其中定义的一波 State 代表各种具体的状态。我打算以以下的步骤才循序渐进,层层深入地介绍 HSM:
- 通过一个实现 TankStateMachine 的 demo 来了解 StateMachine 的使用方式。
- 通过对 demo 中的一些方法的源码走一遍,来了解 HSM 的工作机制。
- 在前面<实例>+<分析源码>的基础上,总结一遍 HSM 的工作流程。
- 总结 HSM 比传统的 State 设计模式的优势到底在哪里。
首先,我们观察到例子中的这些状态具有以下的层级关系(树):
其中,DefaultState 是 root 节点,它是所有状态的前驱状态。而炮筒的安装显然是要在基座安装完毕以后才能进行,故BarrelSetupStartingState,BarrelSetupFinishedState 是 BaseSetupFinishedState 的子状态,等等关系依次类推。
我们自己定义一个 TankStateMachine 继承自 StateMachine 类,作为我们自己的 HSM 实现。我们先来完成这个 demo,关于代码的分析全在代码注释中。
public class TankStateMachine extends StateMachine {
//构造器里要做的工作:State
TankStateMachine(String name) {
//这里先创建出一大波Concrete State的实例来
State mDefaultState = new DefaultState();
State mBaseSetupStartingState = new BaseSetupStartingState();
State mBaseSetupFinishedState = new BaseSetupFinishedState();
State mBarrelSetupStartingState = new BarrelSetupStartingState();
State mBarrelSetupFinishedState = new BarrelSetupFinishedState();
State mBigBarrelSetupStartingState = new BigBarrelSetupStartingState();
State mBigBarrelSetupFinishedState = new BigBarrelSetupFinishedState();
State mAddMissileSuccessedState = new AddMissileSuccessedState();
State mAddRocketSuccessedState = new AddRocketSuccessedState();
//按照层级关系将状态添加到状态树中,注意,下面的缩进空格是用于表达树的层次关系的
addState(mDefault);
addState(mBaseSetupStartingState, mDefault);
addState(mBaseSetupFinishedState, mDefault);
addState(mBarrelSetupStartingState, mBaseSetupStartingState);
addState(mBarrelSetupFinishedState, mBaseSetupFinishedState);
addState(mAddMissileSuccessedState, mBarrelSetupFinishedState);
addState(mBigBarrelSetupStartingState, mBaseSetupFinishedState);
addState(mBigBarrelSetupFinishedState, mBaseSetupFinishedState);
addState(mAddRocketSuccessedState, mBarrelSetupFinishedState);
//设置tank的初始状态为DefaultState,表示tank上还没有安装底座
setInitialState(mDefaultState);
}
//======================下面依次定义所有的状态类============================
//并没有每一个都实现,实现其中的3个
//tank什么都没安装时的状态
class DefaultState extends State {
//状态被进入时(添加到状态栈)被调用
@Override
public void enter() {
log("进入DefaultState!");
}
//状态退出时该方法会被调用
@override
public void exit() {
log("退出DefaultState!");
}
//这里定义状态如何处理 message,这里的 message 不是 State 模式中的 int 类型,而是android //framework中的Message类型,可以表达更多的信息
//这个方法会由 SmHandler 来负责调用,你只需定义当前状态如何处理 Message 即可。
//关于 SmHandler 会在中进行介绍
@override
boolean processMessage(Message message) {
switch (message.what) {
case CMD_SETUP_BASE:
//直接切换到BaseSetupStartingState进行基座安装工作
transitionTo(mBaseSetupStartingState);
return HANDLED;
case CMD_SETUP_BARREL:
case CMD_SETUP_BIG_BARREL:
case CMD_ADD_MISSILE:
case CMD_LAUNCH_MISSILE:
case CMD_LAUNCH_ROCKET:
case CMD_ADD_ROCKET:
//想做上面几件事,都得先去装基座
transitionTo(mBaseSetupStartingState);
//然后再让新状态去处理这个Message吧
deferMessage(message);
return HANDLED;
//底下这些状态都没法处理
case CMD_REMOVE_BASE:
case CMD_REMOVE_BARREL:
case CMD_REMOVE_BIG_BARREL:
case CMD_REMOVE_MISSLE:
case CMD_REMOVE_ROCKET:
case CMD_ATTACK:
default:
return UNHANDLED;
}
return HANDLED;
}
}
//基座安装中的状态
class BaseSetupStartingState extends State {
@Override
public void enter() {
log("进入BaseSetupStartingState!");
//Ok,这里要调用其他真正进行基座安装的类进行基座的实际安装动作
//比如说调用Tank类的旋转基座接口到合适角度的方法,
//发送个消息给其他的类,告诉他们,干活吧,去安装基座
//...
//然后就等待基座安装成功的消息:CMD_BASE_SETUP_SUCCESSED发送过来即可
}
@override
public void exit() {
//停下手头做的一切,
//同时拆除已经安装完成的部分
log("退出BaseSetupStartingState!");
}
@override
boolean processMessage(Message message) {
switch (message.what) {
case CMD_BASE_SETUP_SUCCESSED:
//该条命令表明,其他真正干活的类已经把基座给安装好了,
//TankStateMachine需要切换状态
transitionTo(mBaseSetupFinishedState);
return HANDLED;
case CMD_REMOVE_BASE:
//立刻做一些停止基座安装的操作,再进行已经完成部分的拆除工作
...
//最后切换到DefaultState
transitionTo(mDefaultState);
return HANDLED;
//即使返回false也是交给DefaultState处理,并没有什么用,
//直接true结束这几个cmd
case CMD_ADD_MISSILE:
case CMD_LAUNCH_MISSILE:
case CMD_ADD_ROCKET:
case CMD_LAUNCH_ROCKET:
case CMD_ATTACK:
case CMD_SETUP_BASE:
case CMD_SETUP_BARREL:
case CMD_SETUP_BIG_BARREL:
//这需要接下来转换到的状态去解决些Message
deferMessage(message);
break;
case CMD_REMOVE_BARREL:
case CMD_REMOVE_BIG_BARREL:
case CMD_REMOVE_MISSLE:
case CMD_REMOVE_ROCKET:
default:
//返回false代表当前状态无法处理,
//看看父状态能不能处理吧
return UNHANDLED;
}
//返回true代表Message已经被正确地处理了
return HANDLED;
}
}
//基座安装完毕的状态
class BaseSetupFinishedState extends State {
//......
}
//炮筒安装中的状态,必须要在基座安装完毕才能进入该状态
class BarrelSetupStartingState extends State {
//......
}
//炮筒安装完毕的状态,必须要在基座安装完毕才能进入该状态
class BarrelSetupFinishedState extends State {
//......
}
//大口径炮筒安装中的状态,必须要在基座安装完毕才能进入该状态
class BigBarrelSetupStartingState extends State {
//......
}
//大口径炮筒安装完毕的状态,必须要在基座安装完毕才能进入该状态
class BigBarrelSetupFinishedState extends State {
//......
}
//成功装填普通导弹的状态,必须要在炮筒安装完毕才能进入该状态
class AddMissileSuccessedState extends State {
@Override
public void enter() {
//通知状态显示仪表,已经安装好了导弹
log("进入AddMissileSuccessedState!");
}
@override
public void exit() {
//通知状态显示仪表,导弹被移除
//把导弹移除掉!
log("退出AddMissileSuccessedState!");
}
@override
boolean processMessage(Message message) {
switch (message.what) {
case CMD_ATTACK:
//调用各种发射炮弹需要的方法,例如按下控制台的“发射”按钮
//但是又没有子弹了,需要转换到BarrelSetupFinishedState
transitionTo(mBarrelSetupFinishedState);
return HANDLED;
case CMD_REMOVE_MISSLE:
//调用小兵把导弹拿出来
transitionTo(mBarrelSetupFinishedState);
return HANDLED;
case CMD_LAUNCH_MISSILE:
//调用各种发射炮弹需要的方法,例如按下控制台的“发射”按钮
//但是又没有子弹了,需要转换到BarrelSetupFinishedState
transitionTo(mBarrelSetupFinishedState);
return HANDLED;
//这些问题显然应该交给父状态去看看怎么处理,
//并不是当前状态能够处理的
case CMD_SETUP_BASE:
case CMD_REMOVE_BASE:
case CMD_SETUP_BARREL:
case CMD_REMOVE_BARREL:
case CMD_SETUP_BIG_BARREL:
case CMD_REMOVE_BIG_BARREL:
case CMD_ADD_MISSILE:
//log(已经有子弹了!)
case CMD_ADD_ROCKET:
case CMD_LAUNCH_ROCKET:
case CMD_REMOVE_ROCKET:
default:
return UNHANDLED;
}
return HANDLED;
}
}
//成功装填普通火箭的状态,必须要在炮筒安装完毕才能进入该状态
class AddRocketSuccessedState extends State {
//......
}
//=====================下面定义一大波Message============================
//这些消息会通过调用 StateMachine 的 sendMessage(int)方法发给 SmHandler
//进行管理,由 SmHandler 来分发 Message 给 State 进行处理
static final int CMD_BASE = 0;
static final int CMD_ATTACK = CMD_BASE + 1; //有武器就发射;没武器就什么都不干
static final int CMD_SETUP_BASE = CMD_BASE + 2; //安装基座
static final int CMD_REMOVE_BASE = CMD_BASE + 3; //卸载基座
static final int CMD_SETUP_BARREL = CMD_BASE + 4; //安装炮筒
static final int CMD_REMOVE_BARREL = CMD_BASE + 5; //卸载炮筒
static final int CMD_SETUP_BIG_BARREL = CMD_BASE + 6; //安装大口径炮筒
static final int CMD_REMOVE_BIG_BARREL = CMD_BASE + 7; //卸载大口径炮筒
static final int CMD_ADD_MISSILE = CMD_BASE + 8; //装填导弹
static final int CMD_LAUNCH_MISSILE = CMD_BASE + 9; //发射导弹
static final int CMD_REMOVE_MISSLE = CMD_BASE + 10; //移除导弹
static final int CMD_ADD_ROCKET = CMD_BASE + 11; //装填火箭
static final int CMD_LAUNCH_ROCKET = CMD_BASE + 12; //发射火箭
static final int CMD_REMOVE_ROCKET = CMD_BASE + 13; //移除火箭
//..........
//==============以下定义了一些公共方法接口=============
//这些公共方法是给其他类调用,用以控制Tank,至于其他类有什么需求,
//直接在这些公共方法底下接着添加就行了
//这里的实现很简单,就是直接调用StateMachine类自己定义
//的方法sendMessage(Message),发送相应Message给SmHandler即可
//安装基座
public void setupBase() {
sendMessage(CMD_SETUP_BASE);
}
//安装普通口径炮筒
public void setupBarrel() {
sendMessage(CMD_SETUP_BARREL);
}
//装填导弹(普通口径炮筒才能装填)
public void addMissile() {
sendMessage(CMD_ADD_MISSILE);
}
//装填火箭(大口径炮筒才能装填)
public void addRocket() {
sendMessage(CMD_ADD_ROCKET);
}
//卸载底座
public void removeBase() {
sendMessage(CMD_REMOVE_BASE);
}
//安装大口径炮筒
public void setupBigBarrel() {
sendMessage(CMD_SETUP_BIGBARREL);
}
//卸载普通炮筒
public void removeBarrel() {
sendMessage(CMD_REMOVE_BARREL);
}
//卸载大口径炮筒
public void removeBigBarrel() {
sendMessage(CMD_REMOVE_BIGBARREL);
}
//发射,如果已经装填了就发射,无论是装填的是什么;否则就不发射
public void launch() {
sendMessage(CMD_ATTACK);
}
//发射导弹
public void launchMissile() {
sendMessage(CMD_LAUNCH_MISSILE);
}
//发射火箭
public void launchRocket() {
sendMessage(CMD_LAUNCH_ROCKET);
}
//......
}
demo中,我们在Test类中new了一个TankStateMachine的对象实例,这里面发生了什么?我们查看一下StateMachine的构造器:
protected StateMachine(String name) {
mSmThread = new HandlerThread(name);
mSmThread.start();
Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
构造器创建出了HandlerThread,然后再获取到looper,looper维护了一个MessageQueue,最后调用initStateMachine来创建出一个SmHandler实例来处理MessageQueue中的Message:
private void initStateMachine(String name, Looper looper) {
mName = name;
mSmHandler = new SmHandler(looper, this);
}
我们已经知道,HSM(层次状态机)维护了一个状态树。这个树的每一个节点都是一个StateInfo类的对象,StateInfo类是对State类封装,它记录了State内容的同时,还记录了该State的父状态是哪一个State,该类很简单:
/**
715 * Information about a state.
716 * Used to maintain the hierarchy.
717 */
718 private class StateInfo {
719 /** The state */
//被封装的State
720 State state;
721
722 /** The parent of this state, null if there is no parent */
//父状态
723 StateInfo parentStateInfo;
724
725 /** True when the state has been entered and on the stack */
//如果active为true,说明该状态已经被添加进状态栈了,
//也就是说当前的状态已经执行过了enter()方法
726 boolean active;
727
728 /**
729 * Convert StateInfo to string
730 */
731 @Override
732 public String toString() {
733 return "state=" + state.getName() + ",active=" + active + ",parent="
734 + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
735 }
736 }
该类的成员变量除了封装的State对象,代表父节点的parentStateInfo对象,还有一个boolean active,该变量用于表示该状态是否被enter并且添加进了状态栈中。关于被enter和状态栈,在后面会进行说明,暂时还无需关心。Ok,StateInfo类就是一个树的节点类,我们学数据结构的时候一般习惯定义Node类是记录该节点孩子节点的引用(指针),但是这里是反过来,孩子节点指向父节点,因为一个状态节点可能有N个孩子节点,维护起来很不方便,明显是每个孩子节点中只记录其父状态节点来的容易维护。
了解了以上StateInfo类的作用相当于封装State的Node以后,我们就要来考虑这些Node是如何建立起状态树的。demo中我们通过调用addState(State,State)方法来进行建树。
我们打开addState方法的实现:
1274 /**
1275 * Add a new state to the state machine
1276 * @param state the state to add
1277 * @param parent the parent of state
1278 */
1279 protected final void addState(State state, State parent) {
//这里StateMachine直接掉用SmHandler对象的addState(State,State)去建树了
1280 mSmHandler.addState(state, parent);
1281 }
可以看到 StateMachine 的 addState 方法直接委托 SmHandler 对象:mSmHandler 来进行处理。我们先来大致了解一下 SmHandler 为何物(注意!阅读以下内容前你必须已经对 Handler,HandlerThread,looer之间的关系以及工作机制非常了解。否则会遇到阅读困难!)
SmHandler 是 StateMachine 类的静态内部类,它继承自 Handler 类。它是整个 StateMachine 消息接收,分发的核心。StateMachine 在构造器中创建了一个 HandlerThread 线程并启动了它,然后获取了其 looper,looper 维护着线程的 MessageQueue,这个 MessageQueue 将所有发送给状态机的 Message 存储了起来,并且依次交由 SmHandler 进行处理。SmHandler 在此处的作用也就是一个不断取消息,处理消息的处理者。而 StateMachine 中的状态树,消息延迟队列都是由 SmHandler 类的成员来存储的。
Ok,回到上面的addState方法中来,我们要看的是 SmHandle 中的 addState() 方法定义:
/**
1124 * Add a new state to the state machine. Bottom up addition
1125 * of states is allowed but the same state may only exist
1126 * in one hierarchy.
1127 *
1128 * @param state the state to add
1129 * @param parent the parent of state
1130 * @return stateInfo for this state
1131 */
1132 private final StateInfo addState(State state, State parent) {
1133 if (mDbg) {
1134 mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
1135 + ((parent == null) ? "" : parent.getName()));
1136 }
1137 StateInfo parentStateInfo = null;
1138 if (parent != null) {
//先去的映射表里看看parent对应的
//StateInfo有没有存储过,存过就取出引用
1139 parentStateInfo = mStateInfo.get(parent);
1140 if (parentStateInfo == null) {
//parentStateInfo引用为空,说明parent这个State之前
//并没有被记录在的映射表中
1141 // Recursively add our parent as it's not been added yet.
//那就先将parent添加到状态树中,其父状态为null,也就是
//它的parent暂时为null,之后会被更新的,注意这里是个递归调用
1142 parentStateInfo = addState(parent, null);
1143 }
1144 }
1145 StateInfo stateInfo = mStateInfo.get(state);
1146 if (stateInfo == null) {
//新建一个StateInfo对象来封装State对象并且将它添加到映射表里
1147 stateInfo = new StateInfo();
1148 mStateInfo.put(state, stateInfo);
1149 }
1150
1151 // Validate that we aren't adding the same state in two different hierarchies.
//这里表示一个状态之间被添加进来的时候为其指定过一个
//不是null的parent节点,但是现在又要给它指定一个新的parent,
//这是不被允许的,因为这会导致一个节点位于状态树的不同层次上,
//这会导致状态切换的紊乱
1152 if ((stateInfo.parentStateInfo != null)
1153 && (stateInfo.parentStateInfo != parentStateInfo)) {
1154 throw new RuntimeException("state already added");
1155 }
//更新节点的parent引用,指向parent对应的StateInfo,这是建立节点关系
//的直接步骤
1156 stateInfo.state = state;
1157 stateInfo.parentStateInfo = parentStateInfo;
1158 stateInfo.active = false;
1159 if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
1160 return stateInfo;
1161 }
该方法挺清晰明了的,注释里我对一些操作进行了解释,addState 方法概括一下就是告诉 SmHandler ,状态树的每个节点的父节点是谁,然后 SmHandler 将这些 State 对象一个个封装在 StateInfo 中,这些 StateInfo 连着它们的父节点,树也建立起来了,而这棵状态树在哪里?在
HashMap
/**
1924 * Start the state machine.
1925 */
//该方法是将状态树中,从根节点到目标状态节点的路径上的每个状态都enter,并且
//添加到状态栈中。
1926 public void start() {
1927 // mSmHandler can be null if the state machine has quit.
1928 SmHandler smh = mSmHandler;
1929 if (smh == null)
//如果状态机已经退出了,那么mSmHandler可能为null,那么这时候不用进行
//后续操作了,因为此时状态已经切换到了QuitState了,相当于一条状态树枝
//已经enter完了。
1930 return;
1931 /** Send the complete construction message */
//调用SmHandler对象的completeConstruction方法来进行后续构建工作
1932 smh.completeConstruction();
1933 }
跟进到 SmHandler 类中的 completeConstruction 方法:
/**
920 * Complete the construction of the state machine.
921 */
922 private final void completeConstruction() {
923 if (mDbg) mSm.log("completeConstruction: E");
924
925 /**
926 * Determine the maximum depth of the state hierarchy
927 * so we can allocate the state stacks.
928 */
//底下这段代码是要计算出状态树中的最大深度maxDepth,这个maxDepth用于
//创建状态栈(StateInfo数组)的容量
//状态栈记录的是某一状态节点到其根节点的这条状态路径,其长度自然
//不会超过maxDepth
929 int maxDepth = 0;
930 for (StateInfo si : mStateInfo.values()) {
931 int depth = 0;
932 for (StateInfo i = si; i != null; depth++) {
933 i = i.parentStateInfo;
934 }
935 if (maxDepth < depth) {
936 maxDepth = depth;
937 }
938 }
/*
*以上的两重循环是蛮力法的典型应用,每次从映射表mStateInfo中取出一个StateInfo,然后以该StateInfo为节点一个循环数到根,
*这样做显然一个节点已经数过的对应depth,而它的孩子节点在数的时候还要经过它再数一次,这部分我觉得有优化余地,比如在StateInfo中添加一个int depth变量,初值为-1
*这样每次数的时候就可以考虑能否直接使用已经计算过的劳动果实。
*但是这个framework已经这么实现了,那么我们无论使用什么手段,这个
*maxDepth的计算都是需要每个节点,都一个个步数数出来。
*/
939 if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
940
//有了maxDepth,赶紧把它用起来,初始化两个数组:mStateStack和mTempStateStack
941 mStateStack = new StateInfo[maxDepth];
942 mTempStateStack = new StateInfo[maxDepth];
//调用SmHandler类的setupInitialStateStack方法来将状态路径
//上每个状态入栈(状态栈)吧
943 setupInitialStateStack();
944
945 /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
//此时根节点到当前状态节点的一条路径上的所有状态都依次排在了mStateStack中
//了,根状态节点位于mStateStack的0号位
//向MessageQueue的队头发送一个SM_INIT_CMD消息,最后告诉状态机,
//可以开始运作了,一切准备就绪 :D
946 sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
947
948 if (mDbg) mSm.log("completeConstruction: X");
949 }
我们在这个completeConstruction方法中有两个地方需要看一下对应实现:
- setupInitialStateStack方法将状态路径上的每个状态入栈。
- sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));开启状态机的运行。
跟进setupInitialStateStack方法:
/**
1090 * Initialize StateStack to mInitialState.
1091 */
1092 private final void setupInitialStateStack() {
1093 if (mDbg) {
1094 mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
1095 }
1096
//先把mInitialState对应的StateInfo取出来。mInitialState是addState
//完了以后调用setInitialState(State)来设置的,表示你想让状态机启动之前
//就处于的初始状态
1097 StateInfo curStateInfo = mStateInfo.get(mInitialState);
//一个循环,从mInitialState开始,逆流而上,直到根状态节点。
//每向上逆流一步,就把找到的节点放到mTempStateStack中,
//一个个码起来,最后根结点排在mTempStateStack的所有节点的最后
1098 for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
1099 mTempStateStack[mTempStateStackCount] = curStateInfo;
1100 curStateInfo = curStateInfo.parentStateInfo;
1101 }
1102
1103 // Empty the StateStack
//通过把StateStack的栈顶索引置为-1的手段来清空状态栈,
//这是一个简便手法,无需真正地去清mStateStack,这是很没效率的,
//置栈顶索引为-1,直接可以体现栈里"没东西"
1104 mStateStackTopIndex = -1;
1105
//这个方法简单地把mTempStateStack中的内容倒序放置到了mStateStack中,
//也就是状态路径在mTempStateStack中倒了一下,按照根结点位于栈底,
//初始状态(当前状态)位于栈顶的顺序存放在了mStateStack中
1106 moveTempStateStackToStateStack();
1107 }
状态栈已经部署好了,那么就跟进 sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)); 方法看一下状态机怎么打开消息接收的开关,该方法向 MessageQueue 的队头发送了一个 SM_INIT_CMD,SmHandler 在 handleMessage(Message msg) 中对消息进行取出,然后处理:
@Override
778 public final void handleMessage(Message msg) {
779 if (!mHasQuit) {
780 if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
781
782 /** Save the current message */
783 mMsg = msg;
784
785 /** State that processed the message */
786 State msgProcessedState = null;
//检查状态栈初始化构建工作是否已经完成
787 if (mIsConstructionCompleted) {
788 /** Normal path */
//状态栈初始化构建工作已经完成,可以让状态机对该消息进行处理了
789 msgProcessedState = processMsg(msg);
790 } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
791 && (mMsg.obj == mSmHandlerObj)) {
792 /** Initial one time path. */
//一旦进了这个判定,说明状态栈构建工作之前还没完成,但是收
//到了SM_INIT_CMD消息,说明状态栈构建工作目前已经完成了
//把构建完成的标志变量打开,从此状态机开始对各种消息进行处理
793 mIsConstructionCompleted = true;
//这里是真正让状态栈中每一个状态都调用enter的地方,
//下面会跟进这个方法
794 invokeEnterMethods(0);
795 } else {
//如果StateMachine的start方法没有被调用,那么就
//向StateMachine发送消息,是无法进行处理的,会直接
//抛出RuntimeException
//这里有一个疑问,为什么不直接保存这些message,先不处理?
//而要抛出一个RuntimeException呢?
796 throw new RuntimeException("StateMachine.handleMessage: "
797 + "The start method not called, received msg: " + msg);
798 }
799 performTransitions(msgProcessedState, msg);
800
801 // We need to check if mSm == null here as we could be quitting.
802 if (mDbg && mSm != null) mSm.log("handleMessage: X");
803 }
804 }
跟进 invokeEnterMethods(int) 方法:
/**
1002 * Invoke the enter method starting at the entering index to top of state stack
1003 */
//该方法的int参数代表的是从状态栈的哪一个下标开始enter操作
1004 private final void invokeEnterMethods(int stateStackEnteringIndex) {
1005 for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
1006 if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
//从头开始遍历mStateStack,然后依次调用state的enter方法,
//并将StateInfo对象的变量active置为true,表示该状态已经被enter过了
1007 mStateStack[i].state.enter();
1008 mStateStack[i].active = true;
1009 }
1010 }
该方法依次调用状态栈中stateStackEnteringIndex参数以后的状态的enter方法。而enter方法是client程序员实现的,我们在demo中已经为每个Concrete State都重写了enter方法。enter方法的作用就是进行一些进入该状态时的必要工作,enter方法执行完了以后,对应状态才能开始进行消息处理工作(processMessage)。
到目前为止,我们已经分析了,构造器,addState,start等方法的源码,状态机已经进入了运行状态。下面要分析 StateMachine 如何处理Message,如何切换 State,如何将消息延迟处理。
先查看 StateMachine 的 sendMessage 方法:
1611 /**
1612 * Enqueue a message to this state machine.
1613 *
1614 * Message is ignored if state machine has quit.
1615 */
1616 public final void sendMessage(int what) {
1617 // mSmHandler can be null if the state machine has quit.
1618 SmHandler smh = mSmHandler;
1619 if (smh == null)
1620 return;
//如果StateMachine没有退出,那么调用SmHandler的sendMessage将int类型的
//CMD 作为 Message 的 what 成员变量给封装成了 Message 对象,发送到了
// looper 的 MessageQueue 中
1621 smh.sendMessage(obtainMessage(what));
1622 }
再次查看SmHandler的handleMessage方法:
/**
772 * Handle messages sent to the state machine by calling
773 * the current state's processMessage. It also handles
774 * the enter/exit calls and placing any deferred messages
775 * back onto the queue when transitioning to a new state.
776 */
777 @Override
778 public final void handleMessage(Message msg) {
779 if (!mHasQuit) {
780 if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
781
782 /** Save the current message */
783 mMsg = msg;
784
785 /** State that processed the message */
786 State msgProcessedState = null;
787 if (mIsConstructionCompleted) {
788 /** Normal path */
//和之前一次看handleMessage不一样,这次状态栈已经构建完毕,
//进入这个判定
//调用processMsg(msg)来进行处理,同时返回一个State,
//这个State表示的是,处理完了这个Message以后,将切换到哪个State
789 msgProcessedState = processMsg(msg);
790 } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
791 && (mMsg.obj == mSmHandlerObj)) {
792 /** Initial one time path. */
793 mIsConstructionCompleted = true;
794 invokeEnterMethods(0);
795 } else {
796 throw new RuntimeException("StateMachine.handleMessage: "
797 + "The start method not called, received msg: " + msg);
798 }
// msg 已经得到了处理,执行转换操作,转换到 msgProcessedState,
//同时参数列表将 msg 也传递了过去,说明 msg 还有用武之地
799 performTransitions(msgProcessedState, msg);
800
801 // We need to check if mSm == null here as we could be quitting.
802 if (mDbg && mSm != null) mSm.log("handleMessage: X");
803 }
804 }
以上代码段我们要关注的是msgProcessedState = processMsg(msg);和performTransitions(msgProcessedState, msg);这两处。先进processMsg:
/**
952 * Process the message. If the current state doesn't handle
953 * it, call the states parent and so on. If it is never handled then
954 * call the state machines unhandledMessage method.
955 * @return the state that processed the message
956 */
957 private final State processMsg(Message msg) {
//首先获取状态栈的栈顶元素,即当前我们所处的状态对象
958 StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
959 if (mDbg) {
960 mSm.log("processMsg: " + curStateInfo.state.getName());
961 }
962
963 if (isQuit(msg)) {
//如果Message内容是SM_QUIT_CMD,那么切换到QuittingState
964 transitionTo(mQuittingState);
965 } else {
//该循环是从当前状态开始,调用client程序员重写的processMessage方法,
//若返回true,那么问题解决;若返回false,那么问题没有解决,要继续进循环
966 while (!curStateInfo.state.processMessage(msg)) {
967 /**
968 * Not processed
969 */
//当前状态没能解决 Message,那么让当前状态的父状态来代替当前状态
//处理Message,父状态作为当前状态
970 curStateInfo = curStateInfo.parentStateInfo;
971 if (curStateInfo == null) {
972 /**
973 * No parents left so it's not handled
974 */
//当前状态为空,说明之前已经让根节点状态处理过Message了,
//没搞定,那么只有调用StateMachine的unhandledMessage做
//最后一次处理了,该方法client程序员是可以重写的
975 mSm.unhandledMessage(msg);
976 break;
977 }
978 if (mDbg) {
979 mSm.log("processMsg: " + curStateInfo.state.getName());
980 }
981 }
982 }
//返回最后成功处理了Message的那个状态,如果没有,那么就是返回null
983 return (curStateInfo != null) ? curStateInfo.state : null;
984 }
State processMsg(Message msg)方法总结一下,就是先由当前 State 处理 Message,通过 processMessage 方法,返回 false,则沿着状态栈父引用一路向上一个个 processMessage,直到返回 true 或是已经没有父状态为止。然后将最后调用 processMessage 的 state 对象作为返回值返回。
处理了 Message 以后,跟进 performTransitions 的代码,该方法用于进行状态切换的工作:
/**
807 * Do any transitions
808 * @param msgProcessedState is the state that processed the message
809 */
//注释说明已经说得很清楚了,传入的State是处理了信息的 State,也就是最后
//一个执行 processMessage 的 State
810 private void performTransitions(State msgProcessedState, Message msg) {
811 /**
812 * If transitionTo has been called, exit and then enter
813 * the appropriate states. We loop on this to allow
814 * enter and exit methods to use transitionTo.
815 */
//这段注释说明是说如果 State 的 processMessage 执行了 TransitionTo,
//那么要对合适的状态先调用 exit,再调用 enter,
//我们这里的这个循环允许 enter和exit方法也使用transitionTo的。
//第一步是获取状态栈中的栈顶元素,即要切换过程的起始状态
816 State orgState = mStateStack[mStateStackTopIndex].state;
817
818 /**
819 * Record whether message needs to be logged before we transition and
820 * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
821 * always set msg.obj to the handler.
822 */
823 boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);
824
825 if (mLogRecords.logOnlyTransitions()) {
826 /** Record only if there is a transition */
827 if (mDestState != null) {
828 mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
829 orgState, mDestState);
830 }
831 } else if (recordLogMsg) {
832 /** Record message */
833 mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
834 mDestState);
835 }
836
//destState为本次切换的目的状态,该引用是在transitionTo(State)时传入的参数
837 State destState = mDestState;
838 if (destState != null) {
839 /**
840 * Process the transitions including transitions in the enter/exit methods
841 */
//说明client程序员在实现Concrete State,重写processMessage时,
//调用了transitionTo(state)方法,表达了下一步想要切换到哪一个状态的意愿
842 while (true) {
843 if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
844
845 /**
846 * Determine the states to exit and enter and return the
847 * common ancestor state of the enter/exit states. Then
848 * invoke the exit methods then the enter methods.
849 */
//该方法下面会有分析,它就是将要要enter的方法的StateInfo节点
//都放入中转状态栈中,返回目标节点和起始
//节点的公共父节点
850 StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
//该方法下面也会分析,它将起始节点到公共父节点的一条线都exit,同时调整好了状态栈
851 invokeExitMethods(commonStateInfo);
//此步将中转状态栈中的所有状态倒序再添加到状态栈中,这样,
//新的正确的状态栈建立起来了
852 int stateStackEnteringIndex = moveTempStateStackToStateStack();
//从公共父状态开始,向目标节点的一条路径上,对每个节点依次执行
//enter方法,这和start状态机时的初始化步骤
//是一样的过程。
853 invokeEnterMethods(stateStackEnteringIndex);
854
855 /**
856 * Since we have transitioned to a new state we need to have
857 * any deferred messages moved to the front of the message queue
858 * so they will be processed before any other messages in the
859 * message queue.
860 */
//将mDeferredMessages中所有的消息按照原本的顺序添加在了looper的MessageQueue队列头部
862 moveDeferredMessageAtFrontOfQueue();
863 if (destState != mDestState) {
864 // A new mDestState so continue looping
/*如果在enter或是exit这些State的过程中,transitionTo方法
*被调用过,那么我们还要按照上面的流程再走一
*遍,进行一轮切换
*/
865 destState = mDestState;
866 } else {
867 // No change in mDestState so we're done
//切换已经完成了,出循环
868 break;
869 }
870 }
//这是必须的,否则下一个State处理消息时不执行transitionTo,
//都不得不进上面的循环折腾一下
871 mDestState = null;
872 }
873
874 /**
875 * After processing all transitions check and
876 * see if the last transition was to quit or halt.
877 */
878 if (destState != null) {
879 if (destState == mQuittingState) {
880 /**
881 * Call onQuitting to let subclasses cleanup.
882 */
883 mSm.onQuitting();
884 cleanupAfterQuitting();
885 } else if (destState == mHaltingState) {
886 /**
887 * Call onHalting() if we've transitioned to the halting
888 * state. All subsequent messages will be processed in
889 * in the halting state which invokes haltedProcessMessage(msg);
890 */
891 mSm.onHalting();
892 }
893 }
894 }
OK,performTransitions 方法是 HSM 状态切换的核心方法,但是它的流程还是很清晰的。前提条件是我们已知起始节点,目的节点。然后我们先从目的节点开始进行一波中转站调整,然后再从起始节点开始执行一波 exit 方法,出栈。然后把中转栈中那些马上要 enter 的节点倒序添加到状态栈中,然后从公共父状态执行一波 enter,直到目的节点。
接着要进行的操作是把 mDefferedMessages 中的延迟消息添加到消息队列头,这样下一个状态就可以直接先处理延迟队列里的消息了。
以上过程中,如果有状态在 enter,exit 方法中执行了 transitionTo 方法,那么还得做完上述操作后,再重新开始新一轮的状态切换。
我们跟进到 setupTempStateStackWithStatesToEnter 看一下代码实现,这个方法是用来把切换状态后要 enter 的状态依次添加进mTempStateStack中:
private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
1070 /**
1071 * Search up the parent list of the destination state for an active
1072 * state. Use a do while() loop as the destState must always be entered
1073 * even if it is active. This can happen if we are exiting/entering
1074 * the current state.
1075 */
/*
*源码很人性化,有很多注释帮助我们快速理解代码的用途
*这段注释说明,方法要找出目标状态的父状态列表(向根节点方向的路径上的),
*同时还是active的。
*这可以在我们正为当前状态执行exit或是enter时发生
*/
1076 mTempStateStackCount = 0;
//获取目标状态节点
1077 StateInfo curStateInfo = mStateInfo.get(destState);
1078 do {
//逆流而上,节点依次入中转状态栈
1079 mTempStateStack[mTempStateStackCount++] = curStateInfo;
1080 curStateInfo = curStateInfo.parentStateInfo;
//判定自然是还没有跑到根节点以上,同时当前节点是active是true的
1081 } while ((curStateInfo != null) && !curStateInfo.active);
1082
1083 if (mDbg) {
1084 mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
1085 + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
1086 }
//然后将最后一个添加进中转状态栈中的状态返回,这就是切换的起始节点和终止节点的公共最近父状态节点
1087 return curStateInfo;
1088 }
Ok,setupTempStateStackWithStatesToEnter方法简单易懂,该方法马上要执行enter的一条路径上的StateInfo依次入栈,然后返回了目标节点和起始节点的公共父状态节点。跟进invokeExitMethods方法一窥究竟:
/**
988 * Call the exit method for each state from the top of stack
989 * up to the common ancestor state.
990 */
991 private final void invokeExitMethods(StateInfo commonStateInfo) {
992 while ((mStateStackTopIndex >= 0)
993 && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
994 State curState = mStateStack[mStateStackTopIndex].state;
995 if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
996 curState.exit();
997 mStateStack[mStateStackTopIndex].active = false;
998 mStateStackTopIndex -= 1;
999 }
1000 }
invokeExitMethods 方法开始的注释说明已经说得非常清楚了,从状态栈的栈顶开始,依次执行 exit,这样就是从起始节点向公共父状态进发,不断调用 exit,同时不断把节点的 activ e置为 false,表示该节点已经被 exit 了。
跟进一下 moveDefferedMessageAtFrontOfQueue 方法:
/**
1014 * Move the deferred message to the front of the message queue.
1015 */
1016 private final void moveDeferredMessageAtFrontOfQueue() {
1017 /**
1018 * The oldest messages on the deferred list must be at
1019 * the front of the queue so start at the back, which
1020 * as the most resent message and end with the oldest
1021 * messages at the front of the queue.
1022 */
//循环是从后向前处理延迟队列,并向消息队列头部添加,这确保了延迟队列
//到消息队列中,顺序保持不变
1023 for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
1024 Message curMsg = mDeferredMessages.get(i);
1025 if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
1026 sendMessageAtFrontOfQueue(curMsg);
1027 }
//最后不忘要清空延迟消息队列
1028 mDeferredMessages.clear();
1029 }
该方法从 mDefferedMessages 的最后一个元素开始,倒序将每一个元素send到消息队列的头部,这样 mDeferredMessages 中的所有消息被按照原 来的顺序添加进了消息队列的头部。最后把 mDeferredMessages 给清空。
上面的分析以后我们已经基本上了解了HSM的工作原理,但是 mDeferredMessages 这个延迟消息队列的建立过程我们还没有看源码。当 client程序员实现 State 时,如果调用了 deferMessage:
/** @see StateMachine#deferMessage(Message) */
1191 private final void deferMessage(Message msg) {
1192 if (mDbg) mSm.log("deferMessage: msg=" + msg.what);
1193
1194 /* Copy the "msg" to "newMsg" as "msg" will be recycled */
1195 Message newMsg = obtainMessage();
1196 newMsg.copyFrom(msg);
1197
1198 mDeferredMessages.add(newMsg);
1199 }
这是deferMessage的实现,非常简单,就是向mDefferedMessages后面把该条Message添加进去。
关于HSM源码分析,已经基本上完成了,但是看了源码以后,我们发现StateMachine自己还实现了两个Concrete State:HaltingState和QuittingState。HaltingState代表着暂停状态,当用户调用transitionToHaltingState时会将状态机切换到这个状态。但是它的方法都是空的,并没有实现,留给了client程序员实现。而QuittingState代表了退出状态,当调用StateMachine的quit时,会向消息队列发送一条SM_QUIT_CMD消息:
private final void quit() {
1203 if (mDbg) mSm.log("quit:");
1204 sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
1205 }
然后SmHandler将该消息交给State处理,在调用processMsg时,一上来就要判断该消息是否是SM_QUIT_CMD:
if (isQuit(msg)) {
965 transitionTo(mQuittingState);
966 }
是的话就切换状态,在performTransitionTo方法中走正常流程进入QuittingState。
我们当时在分析performTransitionTo时,最后一段有几行没分析的,这一段就是针对如果切换的目的状态如果是QuittingState或 HaltingState时的操作:
874 /**
875 * After processing all transitions check and
876 * see if the last transition was to quit or halt.
877 */
878 if (destState != null) {
879 if (destState == mQuittingState) {
880 /**
881 * Call onQuitting to let subclasses cleanup.
882 */
//如果是QuittingState,那么先回调StateMachine的onQuitting,
//这是空的,要client程序员实现
883 mSm.onQuitting();
//这步很关键,该方法直接就让整个StateMachine宕机了,什么
//都置空,looper也退了,HandlerThread也不要了
//一个mHasQuit变量标识着状态机已经退出了的状态
884 cleanupAfterQuitting();
885 } else if (destState == mHaltingState) {
886 /**
887 * Call onHalting() if we've transitioned to the halting
888 * state. All subsequent messages will be processed in
889 * in the halting state which invokes haltedProcessMessage(msg);
890 */
//client实现吧
891 mSm.onHalting();
892 }
893 }
同时,我们还注意到除了quit()方法,还有一个quitNow方法,它和quit不同的是,quit要等到之前消息队列里的所有Message处理完了再进行quit流程,而quitNow则直接放弃了消息队列里没有执行到的Message:
private final void quitNow() {
1209 if (mDbg) mSm.log("quitNow:");
//将SM_QUIT_CMD直接发到队头,相当于给队列中所有的其他Message判了死刑
1210 sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
1211 }
在 Step1 和 Step2 中,我们分别实现了一个 demo 和剖析了 HSM 的源代码实现。我们已经对 android framework 中的 HSM 有了一个比较深入的理解,现在还需要再进行一遍梳理,以求巩固。
状态机运行时的消息分发是靠 HandlerThread 线程的 looper 所维护的 MessageQueue 来统治的,而 StateMachine 的内嵌类 SmHandler则对这个MessageQueue中的消息不断地取出处理。状态机的构造器将这个 HandlerThread 跑起来,SmHandler 实例创建出来。我们通过addState方法创建出了一棵能够体现状态层次结构的状态树,这个树保存在 HashMap
如果我们想要退出状态栈,只需调用quit或quitNow,quit可以让状态机处理完 MessageQueue 中的消息再退出,quitNow 则通过将SM_QUIT_CMD 消息发到 MessageQueue 队头的方式立刻结束状态机的生命周期。
HSM 比起 State 模式自然是高级了许多,它高级在能够处理复杂的状态切换以及消息被维护在了队列中,这样使得状态机强大很多。毕竟处理一个消息有时候是很耗时的,你总不能让每个需要状态机的人都阻塞等待吧,而Handler的介入无疑是极大提升消息处理性能的。
当我们设计一个State的时候,对于某一个Message,我们只需要考虑两点:这个Message我能处理吗,能处理就处理,不能处理就返回false吧。第二个考虑,处理完了要切换吗?要切换就切换。然后exit,enter都是基本上独立的,无需为了可能切换到的每个状态都写一大段环境设置的程序。只能说设计模式真的很有魅力,很有学问的,面向接口编程而不是面向实例编程,降低耦合,面向拓展,而不是面向修改,在这个State模式和HSM中都得到了充分的展现,值得好好研究总结体会。
最后看一下<百度百科>中的说法:
“Hierarchical State Machine的缩写。层次状态机是状态机理论中的一种层次结构的模型,各个状态按照树状层次结构组织起来,状态图是层次结构的,也就是说每个状态可以拥有子状态。这个主意很简单,效果却异常强大。从人类思维的角度来看,平面状态模型无法进行扩展。当状态和转换的数量增长时,平面模型就会变得难于理解。而层次状态模型则可以分部分考察,每一部分理解起来相对就变得简单了。以层次结构组织状态行为是最直观的方法,也是实现事件驱动系统的一种很好的形式方法。主要用来描述对象、子系统、系统的生命周期。通过层次状态机可以了解到一个对象能到达的所有状态以及对象收到的事件对对象状态的影响等。状态机指定对象的行为以及不同状态行为的差异。同时,它还能说明事件是如何改变一个对象的状态,因此非常适用于软件开发。
层次状态机较之经典的状态机,最重要的改进就是引入了层次式状态。状态层次嵌套的主要特性来自抽象与层次的结合。这是一种降低复杂性的传统途径,也就是软件中的继承。在面向对象中,类继承概念描述了类和对象之间的关系。类继承描述在类中的is-a 关系。在嵌套状态中,只需用is-in状态关系代替is-a类关系,即它们是等同的分类法。状态嵌套允许子状态继承来自其超状态的状态行为,因此它被称为行为继承。”