android状态机是线程么,[Android] 状态机 StateMachine 源码剖析

1. 案例

案例:我们常见的汽车,我们可以使用它行驶,也可以将它停止在路边。当它在行驶的过程中,需要不断的检测油量,一旦油量不足的时候,就将陷入停止状态。而停止在路边的汽车,需要点火启动,此时将检测车中的油量,当油量不足的时候,汽车就需要去加油站加油。

当我们对汽车的状态和行为进行抽象,汽车的状态可以有 :

停车 STOP

行驶 RUN

检测油量 CHECK_OIL

加油 ADDING_OIL

而我们可以对汽车的操作可以是:

停车 ACTION_STOP

行驶 ACTION_RUN

加油 ACTION_ADD_OIL

我们建立一个二维表,将状态和可操作的行为组合在一起:

a7582506ec45

状态行为对应表

2. HSM

我们通过这个状态表构建我们的状态引用关系模型:

a7582506ec45

状态转化模型

这幅状态图实际上一个相对复杂的网状图形,当构建一个更为复杂的系统的时候,这种网状图将会以成倍的复杂性递增。为了解决这个问题,我们需要将这种网状的状态机转化为一个树状的层次状态机,也叫 HSM (Hierarchical State Machine)。我们可以将上述的状态模型转化为:

a7582506ec45

层次状态图

这张图里,将 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 源码的含义,并且能基于它,开发更多个性化的功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值