1. 案例
案例:我们常见的汽车,我们可以使用它行驶,也可以将它停止在路边。当它在行驶的过程中,需要不断的检测油量,一旦油量不足的时候,就将陷入停止状态。而停止在路边的汽车,需要点火启动,此时将检测车中的油量,当油量不足的时候,汽车就需要去加油站加油。
当我们对汽车的状态和行为进行抽象,汽车的状态可以有 :
- 停车 STOP
- 行驶 RUN
- 检测油量 CHECK_OIL
- 加油 ADDING_OIL
而我们可以对汽车的操作可以是:
- 停车 ACTION_STOP
- 行驶 ACTION_RUN
- 加油 ACTION_ADD_OIL
我们建立一个二维表,将状态和可操作的行为组合在一起:
2. HSM
我们通过这个状态表构建我们的状态引用关系模型:
这幅状态图实际上一个相对复杂的网状图形,当构建一个更为复杂的系统的时候,这种网状图将会以成倍的复杂性递增。为了解决这个问题,我们需要将这种网状的状态机转化为一个树状的层次状态机,也叫 HSM (Hierarchical State Machine)。我们可以将上述的状态模型转化为:
这张图里,将 STOP
作为根节点,从层次上作为其他状态节点的父节点。
-
STOP
作为初始状态 - 发生了
ACTION_ADD_OIL
动作,STOP
状态就变成了ADDING_OIL
状态 - 当
ADDING_OIL
结束,发生了ACTION_RUN
动作,就需要弹出ADDING_OIL
状态 ,传入到CHECK_OIL
,然后传入RUN
状态。
3. [StateMachine] 初始化
StateMachine
是 Android
系统提供的 HSM
状态机的实现,它的源码在包com.android.internal.util
下。StateMachine
提供了三个构造方法,但这三个方法大同小异:
protected StateMachine(String name) {
mSmThread = new HandlerThread(name);
mSmThread.start();
Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
构造器调用 initStateMachine
函数,这个函数需要传入了一个 Looper
对象,StateMachine
对象所有的操作都需要在这个 Looper
所在的线程中运行。而之间的通讯是通过 SmHandler
对象传递。
private void initStateMachine(String name, Looper looper) {
mName = name;
mSmHandler = new SmHandler(looper, this);
}
上面我们说了,StateMachine
是 HSM
状态机,构造它的时候,需要指定它的层次关系,这需要调用 addState
函数,这个函数有两个参数,第第二个参数代表的是第一个参数的父节点:
protected final void addState(State state, State parent) {
mSmHandler.addState(state, parent);
}
而根节点,又称为初始状态节点,需要通过 setInitialState
函数指定:
protected final void setInitialState(State initialState) {
mSmHandler.setInitialState(initialState);
}
这里,不论设置什么样的节点,都需要通过 mSmHandler
对象设置,比如,当通过调用 StateMachine.addState
添加节点的时候,需要调用到 SmHandler.addState
函数:
//code SmHandler
private final StateInfo addState(State state, State parent) {
if (mDbg) {
//debug开关可以通过 StateMachine.setDbg接口设置打开
mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
+ ((parent == null) ? "" : parent.getName()));
}
StateInfo parentStateInfo = null;
// StateInfo 表示在 HSM 树中的状态节点
if (parent != null) {
parentStateInfo = mStateInfo.get(parent);
//mStateInfo 是一个hashmap对象
if (parentStateInfo == null) {
//当父节点不存在的时候,添加该节点
// Recursively add our parent as it's not been added yet.
parentStateInfo = addState(parent, null);
}
}
StateInfo stateInfo = mStateInfo.get(state);
//通过状态构建一个状态节点
if (stateInfo == null) {
stateInfo = new StateInfo();
mStateInfo.put(state, stateInfo);
}
// Validate that we aren't adding the same state in two different hierarchies.
if ((stateInfo.parentStateInfo != null)
&& (stateInfo.parentStateInfo != parentStateInfo)) {
//不允许一个节点存在两个父节点
throw new RuntimeException("state already added");
}
stateInfo.state = state;
stateInfo.parentStateInfo = parentStateInfo;
//构建父子的层次关系
stateInfo.active = false;
if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
return stateInfo;
}
mStateInfo
是一个 HashMap
类型的对象, 而 StateInfo
类是用于记录状态 State
对象信息,和父节点信息的 HSM
节点对象
private class StateInfo {
/** The state */
State state;
/** The parent of this state, null if there is no parent */
StateInfo parentStateInfo;
/** True when the state has been entered and on the stack */
boolean active;
}
按照我们刚才对 Car
这个模型的抽象,我们可以定义出一个 Car
的状态机:
public class Car extends StateMachine {
....
public Car(String name) {
super(name);
this.addState(mStopState,null);
//mStopState 作为根节点状态,没有父节点
this.addState(mAddOilState,mStopState);
//mAddOilState 作为mStopState 的子状态
this.addState(mCheckOilState,mStopState);
//mCheckOilState 作为mStopState 的子状态
this.addState(mRunState,mCheckOilState);
//mRunState 作为mCheckOilState 的子状态
this.setInitialState(mStopState);
// mStopState 为初始状态
}
}
当我们构造完我们的树形结构了以后,我们就可以将我们的状态机启动起来,这个启动依赖于 StateMachine.start
函数:
public void start() {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
/** Send the complete construction message */
smh.completeConstruction();//调用SmHandler.completeConstruction
}
StateMachine.start
中调用 SmHandler.completeConstruction
用于提交我们之前的所有操作:
private final void completeConstruction() {
if (mDbg) mSm.log("completeConstruction: E");
/**
* Determine the maximum depth of the state hierarchy
* so we can allocate the state stacks.
*/
int maxDepth = 0;// step1
for (StateInfo si : mStateInfo.values()) {
int depth = 0;
for (StateInfo i = si; i != null; depth++) {
i = i.parentStateInfo;
}
if (maxDepth < depth) {
maxDepth = depth;//找到一个最深的堆栈
}
}
if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
mStateStack = new StateInfo[maxDepth];
mTempStateStack = new StateInfo[maxDepth];//用于计算的临时变量
setupInitialStateStack();//以初始状态为栈底保存到 mStateStack
/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
if (mDbg) mSm.log("completeConstruction: X");
}
按照我们的 HSM
模型,以 STOP
状态为基础状态的时候,那么我们以这个状态为栈底向上延伸,我们可以得到两个栈,分别是:
stack1: [STOP,CHECK_OIL,RUN]
stack2: [STOP,ADD_OIL]
stack1
的最大深度为 3 , stack2
的最大深度为 2 。那么 stack1
就可以应用于 stack2
的情况。 completeConstruction
代码中 step1
段的代码就是这个目的,找到一个最大的栈,用于给所有的栈情况使用。
private final void setupInitialStateStack() {
if (mDbg) {
mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
}
StateInfo curStateInfo = mStateInfo.get(mInitialState);
for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
mTempStateStack[mTempStateStackCount] = curStateInfo;
curStateInfo = curStateInfo.parentStateInfo;
}
//将初始状态位根状态以0->N的顺序存入 tempStack
// Empty the StateStack
mStateStackTopIndex = -1;
moveTempStateStackToStateStack();//将 tempstack 倒叙复制给 stateStack
}
mTempStateStack
是一个中间变量,它存的是倒叙的 mStateStack
。比如我们的初始状态是 RUN
。那么我们需要不断循环将 RUN
的父节点存入 mTempStateStack
得到:
mTempStateStack :[RUN,CHECK_OIL,STOP]
这时候我们需要调用 moveTempStateStackToStateStack
函数将它倒叙复制到 mStateStack
对象中,保证当前状态 RUN
位于栈顶:
mStateStack: [STOP,CHECK_OIL,RUN]
mStateStackTopIndex
变量指向 mStateStack
的栈顶。刚才的这个例子,mStateStackTopIndex
的值为 2 ,指向 RUN
所在的数组索引位置。
到了 start
函数调用的这一步,我们就完成了一个树形数据结构和初始状态的设置,接下来,我们就可以往我们的状态机上发送我们的指令。
4. [StateMachine] 处理消息
我们通过上面的手段构造完一个状态机以后,就可以通过指令让这个状态机去处理消息了。我们先给我们的状态机开一些外部调用的接口:
public interface ICar {
public void run();
public void stop();
public void addOil();
}
public class Car extends StateMachine implements ICar{
....
}
public void func() {
ICar car = new Car("Ford");
car.addOil();
car.run();
car.stop();
}
当我们要向我们的状态机发送指令的时候,需要调用状态机的 sendMessage(...)
函数,这套函数跟 android.os.Handler
提供的 api
的含义一模一样。实际上,状态机在处理这种消息的时候,也是采用 Handler
的方式,而我们上面反复提到的 SmHandler
对象实际上就是 Handler
对象的子类。
public final void sendMessage(int what) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
smh.sendMessage(obtainMessage(what));//通过Handler方式发送消息
}
这样,我们就可以通过这个函数去实现我们的几个接口方法:
public class Car extends StateMachine implements ICar{
...
public void run() {
this.sendMessage(ACTION_RUN);
}
public void stop() {
this.sendMessage(ACTION_STOP);
}
public void addOil() {
this.sendMessage(ACTION_ADD_OIL);
}
}
根据我们对 Handler
类的了解,每当我们通过 Handler.sendMessage
函数发送一个消息的时候,都将在 Looper
的下个处理消息执行的时候,回调 Handler.handleMessage(Message msg)
方法。由于 SmHandler
继承于 Handler
,并且它复写了 handleMessage
函数,因此 , 消息发送之后,最后将回调到SmHandler.handleMessage
方法中。
//code SmHandler
public final void handleMessage(Message msg) {
if (!mHasQuit) {
if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
/** Save the current message */
mMsg = msg;
/** State that processed the message */
State msgProcessedState = null;
if (mIsConstructionCompleted) {
/** Normal path */
msgProcessedState = processMsg(msg);
//由当前状态处理
} else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
&& (mMsg.obj == mSmHandlerObj)) {
/** Initial one time path. */
//执行初始化操作函数
mIsConstructionCompleted = true;
invokeEnterMethods(0);
//当调用
} else {
throw new RuntimeException("StateMachine.handleMessage: "
+ "The start method not called, received msg: " + msg);
}
performTransitions(msgProcessedState, msg);
// We need to check if mSm == null here as we could be quitting.
if (mDbg && mSm != null) mSm.log("handleMessage: X");
}
}
SmHandler.handleMessage
函数主要执行以下几个操作:
- 根据
mHasQuit
判断是否退出,如果退出将不执行后续指令 - 判断是否初始完成(根据变量
mIsConstructionCompleted
),如果初始化完成调用processMsg
将消息抛给当前状态执行 - 如果尚未初始化,并且接受的是初始化命令
SM_INIT_CMD
将执行一次初始化操作 - 当命令执行结束后,执行
performTransitions
函数用于转变当前状态和mStateStack
我们先接着上面第三个主题 [StateMachine] 初始化
看下第三步。 SM_INIT_CMD
指令的发出位于 SmHandler.completeConstruction
函数中:
//code SmHandler
private final void completeConstruction() {
...
/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
...
}
处理初始化消息的时候会先将 mIsConstructionCompleted
设置为 true
,告诉状态机已经初始化过了,可以让状态处理消息了。然后调用了个 invokeEnterMethods
函数。这个函数的目的是回调当前 mStateStack
栈中所有的活动状态的 enter
方法。并且将非活跃状态设置为活跃态:
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
mStateStack[i].state.enter();
mStateStack[i].active = true;
}
}
这样,如果我们的初始状态是 STOP
的话,我们就可以在后台打印中看到:
//console output:
output: [StateMachine] StopState enter
如果我们的初始状态是 RUN
状态的话就可以看到:
//console output:
output: [StateMachine] StopState enter
output: [StateMachine] CheckOilState enter
output: [StateMachine] RunState enter
上面就是处理初始化消息的过程,到这一步,初始化的过程算是完整走完。我们继续来看初始化后的逻辑,当初始化已经结束之后,再收到的消息将通过 processMsg
函数提交给合适的状态执行。
private final State processMsg(Message msg) {
StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
//获取当前状态节点
if (isQuit(msg)) {
//判断当前消息是否是退出消息
transitionTo(mQuittingState);
} else {
while (!curStateInfo.state.processMessage(msg)) {
//当该状态不处理当前消息的时候,将委托给父状态处理
curStateInfo = curStateInfo.parentStateInfo;
if (curStateInfo == null) {
mSm.unhandledMessage(msg);
//当没有状态可以处理当前消息的时候回调unhandledMessage
break;
}
}
}
return (curStateInfo != null) ? curStateInfo.state : null;
}
processMsg
会先判断当前是否是退出消息,如果 isQuit
成立,将转入 mQuittingState
状态。我们将在后面分析如何执行退出操作,这块东西,我们暂且有个印象。当并非退出消息时候,将会分配给当前状态处理,如果当前状态处理不了,将委托给父状态处理。比如当前我们的初始状态是 RUN
。那么对应的 mStateStack
为:
[STOP,CHECK_OIL,RUN]
我们给状态的测试代码是:
private class BaseState extends State {
@Override
public void enter() {
log(" enter "+this.getClass().getSimpleName());
super.enter();
}
@Override
public void exit() {
log(" exit "+this.getClass().getSimpleName());
super.exit();
}
}
public class StopState extends BaseState {
@Override
public boolean processMessage(Message msg) {
log("StopState.processMessage");
return HANDLED;//处理消息
}
}
public class CheckOilState extends BaseState {
@Override
public boolean processMessage(Message msg) {
log("CheckOilState.processMessage");
return NOT_HANDLED;// 不处理消息
}
}
public class RunState extends BaseState {}
我们往状态机 Car
发送一条消息:
Car car = new Car();
car.sendMessage(0x01);
我们将在后台打印出log:
--> enter StopState
--> enter CheckOilState
--> enter RunState
// 初始化结束
-->[StateMachine]:handleMessage 1
-->CheckOilState.processMessage // run状态不处理,扔给checkoil状态
-->StopState.processMessage // checkoil状态不处理,扔给stop 状态
当然,如果你并不希望消息被委托调用,你可以在初始状态调用 processMessage
函数的时候,返回 HANDLED
常量,这样就不会往下调用。
5. [StateMachine] 状态转换
通常,我们会在 State.processMessage
内部,通过调用 transitionTo
函数执行一次状态转换,而调用这个函数只是将你要转换的状态存入一个临时的对象中:
protected final void transitionTo(IState destState) {
mSmHandler.transitionTo(destState);
}
private final void transitionTo(IState destState) {
mDestState = (State) destState;
}
真正的状态转换将发生在 SmHandler.handleMessage
函数执行之后:
public final void handleMessage(Message msg) {
if (!mHasQuit) {
...
performTransitions(msgProcessedState, msg);//变更状态
}
}
这里将调用 performTransitions
函数完成状态转换,假如,现在的状态是 RUN
状态,当需要转成 ADD_OIL
状态的时候,将进行一下转变:
/**
初始:
mStateStack : [ STOP,CHECK_OIL,RUN]
*/
private void performTransitions(State msgProcessedState, Message msg) {
State orgState = mStateStack[mStateStackTopIndex].state;
//orgState记录当前状态
State destState = mDestState;
//destState 记录要转变的目标状态
if (destState != null) {
while (true) {
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
//查找跟目标状态的公共节点状态,此时为 STOP 状态节点
invokeExitMethods(commonStateInfo);
//从栈顶一直到commonStateInfo(不包含) 所在的位置执行退出操作
int stateStackEnteringIndex = moveTempStateStackToStateStack();
invokeEnterMethods(stateStackEnteringIndex);
moveDeferredMessageAtFrontOfQueue();
//将Deferred 消息放入队列头部优先执行
if (destState != mDestState) {
destState = mDestState;
} else {
break;
}
}
mDestState = null;
}
if (destState != null) {
if (destState == mQuittingState) {
//TODO clean
} else if (destState == mHaltingState) {
//TODO halt
}
}
}
这段代码执行的时候,会先去寻找目标节点和当前节点的公共祖先节点,这是通过调用 setupTempStateStackWithStatesToEnter
调用的。StateMachine
的函数名起的见名知意,*Temp*
代表这个函数中要使用中间变量 mTempStateStack
。*ToEnter
代表需要对添加进的状态执行 State.enter
操作。
private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
mTempStateStackCount = 0;//重置 mTempStateStack
StateInfo curStateInfo = mStateInfo.get(destState);
do {
mTempStateStack[mTempStateStackCount++] = curStateInfo;
curStateInfo = curStateInfo.parentStateInfo;
} while ((curStateInfo != null) && !curStateInfo.active);
//找到第一个 active 的状态节点。
return curStateInfo;
}
setupTempStateStackWithStatesToEnter
函数就是将目标节点的堆栈复制到 mTempStateStack
变量中,然后将最终相交的节点返回。这里采用 do-while
的写法,说明这个函数的执行,至少包含一个 destState
元素。刚才从 RUN->ADD_OIL
的例子中,setupTempStateStackWithStatesToEnter
将返回 STOP
状态,mTempStateStack
的为:
mTempStateStack: {ADD_OIL}
我们回到 performTransitions
的流程,执行 setupTempStateStackWithStatesToEnter
完,将执行 invokeExitMethods
函数。
private final void invokeExitMethods(StateInfo commonStateInfo) {
while ((mStateStackTopIndex >= 0)
&& (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
State curState = mStateStack[mStateStackTopIndex].state;
curState.exit();
mStateStack[mStateStackTopIndex].active = false;
mStateStackTopIndex -= 1;
}
}
这个函数相当于将 mStateStack
栈中的非 commonStateInfo
进行出栈。
mStateStack: {STOP,CHECK_OIL,RUN} ->
invokeExitMethods(STOP) ->
mStateStack: {STOP}
执行完出栈后,只需要将我们刚才构建的 mTempStateStack
拷贝到 mStateStack
就可以构建新的状态栈了,而这个操作是通过 moveTempStateStackToStateStack
函数完成,而 moveTempStateStackToStateStack
我们刚才说过,实际上就是将 mTempStateStack
逆序赋值到 mStateStack
。这样,我们就构建了一个新的 mStateStack
:
mStateStack: {STOP,ADD_OIL}
这个时候,我们构建了一个新的状态栈,相当于已经切换了状态。performTransitions
在执行完 moveTempStateStackToStateStack
之后,调用 invokeEnterMethods
函数,执行非 active
状态的 enter
方法。之后执行 moveDeferredMessageAtFrontOfQueue
将通过 deferMessage
函数缓存的消息队列放到 Handler
消息队列的头部:
...
int stateStackEnteringIndex = moveTempStateStackToStateStack();
invokeEnterMethods(stateStackEnteringIndex);
moveDeferredMessageAtFrontOfQueue();
//将Deferred 消息放入队列头部优先执行
if (destState != mDestState) {
destState = mDestState;
} else {
break;
}
...
当我们完成状态的转换了以后,需要对两种特殊的状态进行处理,在 performTransitions
函数的末尾会判断两个特殊的状态:
1. HaltingState
2. QuittingState
6. 状态机的退出
状态机的退出,StateMachine
提供了几个方法:
- quit: 执行完消息队列中所有的消息后执行退出和清理操作
- quitNow: 抛弃掉消息队列中的消息,直接执行退出和清理操作
- transitionToHaltingState: 抛弃掉消息队列中的消息,直接执行退出,不做清理
从上面的表述中看,quit
相对 halt
操作来说更加的安全。这个 Thread
的 intercept
和 stop
方法很类似,很好理解。上面我们说到,退出状态 HaltingState
和 QuittingState
是在performTransitions
函数的末尾判断和执行的,我们来看下代码:
if (destState != null) {
if (destState == mQuittingState) {
mSm.onQuitting();
cleanupAfterQuitting();//清理操作
} else if (destState == mHaltingState) {
mSm.onHalting();//只是执行回调
}
}
private final void cleanupAfterQuitting() {
if (mSm.mSmThread != null) {
getLooper().quit();//退出线程
mSm.mSmThread = null;
}
/*清空数据*/
mSm.mSmHandler = null;
mSm = null;
mMsg = null;
mLogRecords.cleanup();
mStateStack = null;
mTempStateStack = null;
mStateInfo.clear();
mInitialState = null;
mDestState = null;
mDeferredMessages.clear();
mHasQuit = true;
}
当 destState == mQuittingState
语句成立,将回调 StateMachine.onQuitting
函数,之后将执行 cleanupAfterQuitting
进行清理操作。清理操作中,会将线程清空,和其他数据变量清空,而如果 destState == mHaltingState
成立,StateMachine
将不执行任何的清理操作,通过回调 onHalting
函数来通知状态机退出。
7. 总结
Android
里面的这个 StateMachine
状态机在很多源码中都有涉及,代码也很简单,没有什么太大的难度,希望以上的总结能帮各位看官理解 StateMachine
源码的含义,并且能基于它,开发更多个性化的功能