Android Framework 状态机实现原理
Posted Nipuream
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Framework 状态机实现原理相关的知识,希望对你有一定的参考价值。
android Framework 状态机实现原理
前言
状态模式是比较一种常见的设计模式,为了解决某个事物很多状态之间的切换很多的业务逻辑,这样可以避免很多if-else嵌套,不仅提高了代码的可读性,同样拓展性也得到很大的提升。在Android系统中,像wifi、蓝牙的源码中,看到状态机的使用,所以来学习下。状态机是对状态模式的一种拓展,使用了Handler机制来对消息的分发,同时还增加了 state-hierarchy 的模式,状态同样也有父状态,如果当前状态没有能力去处理当前消息,会去让父状态来处理。下面会从使用的角度来分析源码,来深入理解Android Framework 状态的机制。
我们使用Android源码中给的例子:
//首先初始化状态机,然后调用start()方法
public static Hsm1 makeHsm1()
System.out.println("makeHsm1 E");
Hsm1 sm = new Hsm1("hsm1"); //Hsm1 extend StateMachine
sm.start();
System.out.println("makeHsm1 X");
return sm;
//状态机的构造方法
Hsm1(String name)
super(name);
System.out.println("ctor E");
//添加状态,其中mP1是mS1和mS2的父状态,
addState(mP1);
addState(mS1, mP1);
addState(mS2, mP1);
addState(mP2);
// 需要设置一个初始状态
setInitialState(mS1);
System.out.println("ctor X");
//调用状态机方法,让状态机运行起来,然后发消息
Hsm1 hsm1 = Hsm1.makeHsm1();
hsm1.sendMessage(hsm1.obtainMessage(Hsm1.CMD_1));
hsm1.sendMessage(hsm1.obtainMessage(Hsm1.CMD_2));
状态机初始化
状态机的初始化是调用了StateMachine.start()方法,看下源码:
public void start()
// 确保handler创建了
SmHandler smh = mSmHandler;
if (smh == null) return;
/** Send the complete construction message */
smh.completeConstruction();
private final void completeConstruction()
if (mDbg) mSm.log("completeConstruction: E");
//计算每种状态的最大深度,maxDepth就是所有状态中,父状态最多的的路径长度
int maxDepth = 0;
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();
//发送init指令,其实是触发了初始状态的执行
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
if (mDbg) mSm.log("completeConstruction: X");
这里为什么要创建两个状态栈呢?因为例如一个状态有一个父状态,当切换到这个状态的时候肯定是父状态先执行enter,然后在子状态执行enter,退出这个状态的时候,肯定要先子状态执行exit,然后父状态再执行exit,但是栈堆的机制就是后进先出,所以只能用两个栈堆来确保enter和exit的执行顺序。下面来看看 上面代码的 setupInitialStateStack()。
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;
// Empty the StateStack
mStateStackTopIndex = -1;
moveTempStateStackToStateStack();
private final int moveTempStateStackToStateStack()
int startingIndex = mStateStackTopIndex + 1;
int i = mTempStateStackCount - 1;
int j = startingIndex;
while (i >= 0)
if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
mStateStack[j] = mTempStateStack[i];
j += 1;
i -= 1;
mStateStackTopIndex = j - 1;
if (mDbg)
mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
+ ",startingIndex=" + startingIndex + ",Top="
+ mStateStack[mStateStackTopIndex].state.getName());
return startingIndex;
上面的代码执行逻辑解释了我之前所说,所以mStateStack和mTempStateStack里面的顺序完全是相反的,这么做的目的是为了确保父状态和子状态 enter和exit方法执行顺序的正确性,这也是他们当初设计的想法吧。
状态机处理消息
在Handler的处理机制,我们很容易得到消息的处理都是在handleMessage()方法中执行的,下面一起看下状态机对这个方法的重写吧?
@Override
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)
//2.正常的逻辑
msgProcessedState = processMsg(msg);
//1.还记得状态机初始化过程中最后的一步吧 => sendMessageAtFrontOfQueue
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);
//3.切换状态
performTransitions(msgProcessedState, msg);
// We need to check if mSm == null here as we could be quitting.
if (mDbg && mSm != null) mSm.log("handleMessage: X");
首先来看看第一步,当状态机接收到了SM_INIT_CMD消息的时候,做了哪些事情:
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;
很简单,就是执行栈中所有状态的enter方法,且置为激活状态,哪个先执行,显然是父状态吧?再来看看第二步,这个是正常执行的逻辑:
private final State processMsg(Message msg)
StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
if (mDbg)
mSm.log("processMsg: " + curStateInfo.state.getName());
if (isQuit(msg))
//如果状态是退出状态,则切换至 mQuittingState状态,先不看
transitionTo(mQuittingState);
else
//执行状态类的processMessage方法,如果当前状态类无法处理会返回false
//交于父状态处理,如果所有的父状态都无法处理则调用 状态机的unhandleMessage方法
while (!curStateInfo.state.processMessage(msg))
/**
* Not processed
*/
curStateInfo = curStateInfo.parentStateInfo;
if (curStateInfo == null)
/**
* No parents left so it's not handled
*/
mSm.unhandledMessage(msg);
break;
if (mDbg)
mSm.log("processMsg: " + curStateInfo.state.getName());
return (curStateInfo != null) ? curStateInfo.state : null;
每个状态类都是继承于IState接口,其processMessage都是由开发者复写,返回false就是当前状态类无法处理,true代表已经处理了。最后unhandleMessage,状态机也只是打印了一下而已:
protected void unhandledMessage(Message msg)
if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);
接着,我们再继续分析第三步(切换状态), performTransitions(msgProcessedState, msg) :
private void performTransitions(State msgProcessedState, Message msg)
//原始状态
State orgState = mStateStack[mStateStackTopIndex].state;
//记录日志
//...
//目的状态,由transitionTo方法切换状态
State destState = mDestState;
if (destState != null)
//执行状态切换中,enter和exit的方法执行
while (true)
if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
//利用stack和 temp stack 完成状态切换过程中enter和exit方法的执行,
//这里不准备详细讲了,后面用图来分析,有兴趣可以自行分析
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;
//...
可以看到如果从C 状态切换到E状态,首先我们要做的事情就是想C状态以及C状态的父状态先执行exit方法,执行顺序是C->B->A,但是A也是E的祖宗状态,从C切换至E,不能执行A的exit状态,所以上面很多的代码做的就是这个事情,所以由C切换至E的代码逻辑就是 C.exit -> B.exit -> D.enter->E.exit。
demo源码已上传github. 状态机原理
以上是关于Android Framework 状态机实现原理的主要内容,如果未能解决你的问题,请参考以下文章
Android 系统 Framework 中定制实现开关机动画实践
Android 系统 Framework 中定制实现开关机动画实践