状态机模式可以帮助我们对复杂的软件行为进行建模,避免引入复杂的很多swich…case…或if…else…逻辑,导致代码很难维护。状态机构成的三要素是状态、消息和动作,即某个状态收到某条消息后执行某项动作(处理消息,若有需要执行状态迁移)。
Android在框架层定义的状态机类(frameworksbasecorejavacomandroidinternalutilStateMachine.java),其基于Android的Handler-Looper_Message机制构建。利用面向对象思想,进一步引入层次化(状态之间构成父子关系)状态机模型。父状态代表一种基本状态,子状态是包含了基本状态的特定状态。消息可以在子状态做细化具体处理,当子状态无法处理该消息时,就交给其父状态做基本处理(注意,这里的父状态和子状态在类定义上并不构成继承关系,他们均是状态的子类,即互相是兄弟类关系。他们只是在进入状态时或处理消息时具备传递链关系),以进一步减少代码冗余度
The state machine defined here is a hierarchical state machine which
processes messages and can have states arranged hierarchically.
通过对Android StateMachine类的学习,我们可以进一步深入理解Android的Handler-Looper-Message机制的运用,并有助于我们利用状态模型建模。
先看看Android StateMachine对需求的定义:
当一个StateMachine被created和started之后,通过sendMessage()方法向StateMachine发送消息,消息通过Message.obtainMessage创建,当一个状态机收到消息之后,当前状态的processMessage方法会被调用。
StateMachine里面的每个状态都有零个或一个父状态,如果子状态无法处理这个消息,那么子状态的processMessage方法返回false或者NOT_HANDLED,接下来该条消息就会交给父状态的processMessage执行,这样延续下去,如果所有状态都未处理该消息,那么状态机的unhandledMessage方法会被调用,用来给消息最后一次执行机会。
当所有状态都被初始加入状态机并设定好层次(父子关系)后,跳转到一个新的状态会导致当前状态及其祖先状态的退出且新状态及其祖先状态的进入。如果当前状态和新状态具有共同的祖先状态,则当前状态一直到共同祖先状态间(不含共同祖先状态)的exit方法会被调用,然后又从共同祖先到新状态间的子状态开始一直到新状态,这些状态的enter方法也会被调用,并最终进入新状态。
当StateMachine构建好后,调用其start方法来启动状态机,启动后的第一个动作是从初始状态最顶层的祖先状态开始逐层调用enter方法。上述enter完成后接下来会进行消息队列中消息的处理。
StateMachine正常的退出,可以调用quit或者abort,调用quit会退出当前状态和他的父状态,调用onQuiting之后退出Thread和Looper。
主要相关类有State 、StateMachine 、 SmHandler(StateMachine的内部类)、 StateInfo(SmHandler的内部类)。
首先看看State类,这个类顾名思义,定义了状态本身,我们在开发活动中抽象的状态均可从该类派生,其主要有这样几个关键方法:
enter() //在进入状态时调用
exit() //在退出状态时调用
processMessage(Message) //当状态机处理消息时会调用当前状态的这个方法,
对StateMachine类,其下面几个方法:
StateMachine() //构造函数中会构建出SmHandler(状态机处理器,后面会介绍),并将其与某个Looper绑定,根据构造函数参数的不同,也可能会构建出一个HandlerThread作为驱动状态机并提供Looper的线程。
addState()//通过SmHandler的同名接口向状态机添加状态,并指明添加状态的父状态(非类定义上的父子状态关系),
setInitialState()//通过SmHandler的同名接口设置状态机的初始状态
getCurrentState() //通过SmHandler的同名接口获取当前状态
transitionTo()//通过SmHandler的同名接口执行状态迁移,使状态机当前状态发生切换
obtainMessage() //获取一条交给SmHandler的消息
sendMessage()//向SmHandler发送消息
deferMessage() //通过SmHandler的同名接口将消息推迟到下次状态迁移后进行消息处理,所有这些推迟的消息将放置到下次状态完成迁移后的消息队列前端优先处理。
sendMessageAtFrontOfQueue()//与deferMessage()相反,这个是通过SmHandler的同名接口将消息发送到消息队列的前端供当前状态优先处理。。
deferMessage和sendMessageAtFrontOfQueue这两个方法属于protected,只能在状态机及其子类中调用,不能被外部调用。
start() //启动状态机运行,实际上是通过SmHandler.completeConstruction()完成SmHandler的初始化,且发送一条SM_INIT_CMD消息。关于completeConstruction后面还会介绍。
SmHandler类
不难发现,上面的StateMachine类只是个对外暴露接口的封装代理类,真正的代码处理逻辑都是放在SmHandler这个类里面。
SmHandler类继承自Handler类,其内部还有个内部类名为StateInfo.先看看StateInfo的定义:
private class StateInfo { State state; //当前状态信息类的父类,实际上就是维护了当前状态的父状态 StateInfo parentStateInfo; //当为true时,表示该状态是作为当前状态或当前状态的父状态已通过state.enter()方法被激活 boolean active; 。。。 }
由此可见,StateInfo就是一个状态类的包裹,但其维护了包裹状态的父状态信息,同时标记了这个状态是否已enter()但还未exit(),这个active属性可用于在状态迁移时查找当前状态和新状态的共同祖先状态。 SmHandler在初始化过程的addState方法中基于HashMap构建了一个状态到状态信息的哈希映射表:
private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
在SmHandler中,还基于数组维护了两个栈,这两个栈的初始化即在前面提到过的SmHandler.completeConstruction()方法中:
//基于初始化层次状态树的最大深度构建一个运行状态栈,这个栈顶标记的状态即为状态机当前运行状态,这个栈的主要用途是为了完成状态迁移(涉及到当前状态的父状态的退出和新状态及其父状态的退出)。位于这个栈上的状态均标记为活跃状态,即其active属性为true.
private StateInfo mStateStack[]; //mStateStack的栈顶标记,mStateStack[mStateStackTopIndex]即为状态机的当前状态。 private int mStateStackTopIndex = -1; //基于初始化层次状态树的最大深度构建的辅助临时状态栈,协助完成状态迁移和mStateStack的初始化
private StateInfo mTempStateStack[];
//在StateMachine初始化时,会将初始状态一直到它的顶级祖先状态通过mTempStateStack栈反序加入到mStateStack中,即最终初始状态顶级祖先状态会位于mStateStack栈底,而初始状态会位于mStateStack栈顶,在这之间是初始状态一直到顶级祖先状态的父状态。
另外还预定义了两个状态:
//挂起状态,当状态机挂起时进入
private HaltingState mHaltingState = new HaltingState();
//退出状态,当状态机退出时进入
private QuittingState mQuittingState = new QuittingState();
handleMessage(Message msg)
//不消说,继承自Handler类的SmHandler必然充定义了该消息处理方法,该方法的主要流程就是将消息交给mStateStack栈顶状态进行处理,如果栈顶状态不能处理该消息,就交给栈顶状态的父状态处理,一直这样延续下去直到消息被处理或父状态为空为止。如父状态为空消息都还未得到处理,就将调用unhandledMessage执行最后一次处理机会。
transitionTo(IState destState) //这个接口属于protected,在状态机内部调用,用于指定迁移到某个状态,其实内部实现很简单,就是在SmHandler中指定了一个迁移目标状态,在接下来的消息处理过程中,如果发现目标迁移状态非空,就会通过下面的performTransitions()方法来实现状态迁移:
/** * Do any transitions * @param msgProcessedState is the state that processed the message */ private void performTransitions(State msgProcessedState, Message msg) { //设定迁移前的原生状态 State orgState = mStateStack[mStateStackTopIndex].state; 。。。 //中间这段是日志记录代码,略。 。。。 //设定迁移后的目标状态 State destState = mDestState; if (destState != null) { while (true) { //通过临时状态栈和状态的active标记找到原生状态和目标状态的共同祖先状态,调用从原生状态开始一直到共同祖先状态(不含共同祖先状态间)的exit方法。并设置这些状态的active标记为false.然后将临时状态栈拷贝到运行状态栈中(也就是将临时状态栈的目标状态一直到共同祖先(不含共同祖先状态)状态的状态链反序复制过去,然后执行他们的enter方法。), StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); invokeExitMethods(commonStateInfo); int stateStackEnteringIndex = moveTempStateStackToStateStack(); invokeEnterMethods(stateStackEnteringIndex); moveDeferredMessageAtFrontOfQueue(); //进行一些收尾处理 if (destState != mDestState) { // A new mDestState so continue looping destState = mDestState; } else { // No change in mDestState so we're done break; } } mDestState = null; } /** * After processing all transitions check and * see if the last transition was to quit or halt. */ if (destState != null) { if (destState == mQuittingState) { mSm.onQuitting(); cleanupAfterQuitting(); } else if (destState == mHaltingState) { mSm.onHalting(); } } }
private final StateInfo setupTempStateStackWithStatesToEnter(State destState) { mTempStateStackCount = 0; StateInfo curStateInfo = mStateInfo.get(destState); do { mTempStateStack[mTempStateStackCount++] = curStateInfo; curStateInfo = curStateInfo.parentStateInfo; } while ((curStateInfo != null) && !curStateInfo.active); return curStateInfo; }
private final void invokeExitMethods(StateInfo commonStateInfo) { while ((mStateStackTopIndex >= 0) && (mStateStack[mStateStackTopIndex] != commonStateInfo)) { State curState = mStateStack[mStateStackTopIndex].state; if (mDbg) mSm.log("invokeExitMethods: " + curState.getName()); curState.exit(); mStateStack[mStateStackTopIndex].active = false; mStateStackTopIndex -= 1; } } private final void invokeEnterMethods(int stateStackEnteringIndex) { for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName()); mStateStack[i].state.enter(); mStateStack[i].active = true; } }
private final void invokeEnterMethods(int stateStackEnteringIndex) { for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { mStateStack[i].state.enter(); mStateStack[i].active = true; } }