前言
HI,欢迎来到《每周一博》。今天是十一月第五周,我给大家介绍一下安卓系统中的状态机。为什么会介绍状态机呢?因为我在工作过程中遇到了问题,需要走读系统WiFi的源码,而WiFi的上层实现中状态机占了很大的比例,为了帮助理解WiFi工作过程,需要先学习一下状态机。
一. 状态模式
状态机是状态模式的一种应用,我们先来看一下状态模式。状态模式是一种行为模式,在不同的状态下有不同的行为。状态模式的行为是平行的,不可替换的,比如电梯状态可以分为开门状态,关门状态,运行中状态。状态模式把对象的行为包装在不同的状态对象里,对象的行为取决于它的状态,当一个对象内部状态改变时,行为也随之改变。
举个简单的例子,看微博时点击转发按钮,如果登录了就会跳转到转发界面,如果没登录就会跳转到登录界面,这就是转发行为在用户登录和未登录状态下的不同。再比如电视有开和关两种状态,有调音量,换台,开机,关机等行为,当电视处于关闭状态,只会响应开机指令,而电视处于打开状态,则会响应调音量,换台,关机的指令。通常我们会用if-else来判断电视状态,然后去执行相关指令,但是使用了状态模式之后就不用再写那么多if-else来进行判断了。我们就以此为例,写一个状态模式的代码。
定义抽象电视状态接口,面相抽象,避免依赖具体实现;
public interface TvState{
public void nextChannerl();
public void prevChannerl();
public void turnUp();
public void turnDown();
}
定义电视关机状态,它是抽象电视状态的一个具体实现,在关机状态下什么也不操作;
public class PowerOffState implements TvState{
public void nextChannel(){}
public void prevChannel(){}
public void turnUp(){}
public void turnDown(){}
}
定义电视开机状态,它是抽象电视状态的一个具体实现,在开机状态下可以响应各项指令;
public class PowerOnState implements TvState{
public void nextChannel(){
System.out.println("下一频道");
}
public void prevChannel(){
System.out.println("上一频道");
}
public void turnUp(){
System.out.println("调高音量");
}
public void turnDown(){
System.out.println("调低音量");
}
}
定义状态生效的环境类,用来切换状态,可以理解为状态的代理;
public class TvController {
TvState mTvState;
public void setTvState(TvStete tvState){
mTvState=tvState;
}
public void powerOn(){
setTvState(new PowerOnState());
System.out.println("开机啦");
}
public void powerOff(){
setTvState(new PowerOffState());
System.out.println("关机啦");
}
public void nextChannel(){
mTvState.nextChannel();
}
public void prevChannel(){
mTvState.prevChannel();
}
public void turnUp(){
mTvState.turnUp();
}
public void turnDown(){
mTvState.turnDown();
}
}
编写测试类进行测试;
public class Client{
public static void main(String[] args){
TvController tvController=new TvController();
tvController.powerOn();
tvController.nextChannel();
tvController.turnUp();
tvController.powerOff();
//调高音量,此时不会生效
tvController.turnUp();
}
}
以上就是状态模式的一个简单实现,虽然类增加了不少,但是逻辑清晰简单,如果不用状态模式,我们的代码应该是下面的样子,有很多if-else;
public void prevChannel(){
if(POWER_ON){
System.out.println("上一频道");
}
}
public void turnUp(){
if(POWER_ON){
System.out.println("调高音量");
}
}
二. 状态机
接下来我们看下状态机的原理。状态机是一组状态的集合,是协调相关信号动作,完成特定操作的控制中心。状态机可归纳为4个要素,即当前状态,条件,动作,下个状态。这样的归纳主要出于对状态机的内在因果关系的考虑,当前状态和条件是因,动作和下个状态是果。对于复杂些的逻辑,用状态机会有助于代码比较清晰,容易维护和调试
状态机的用法一般是这样的,发生了某个事件后,根据当前状态,决定执行的动作,并设置下一个状态。
1. 状态声明
接下来我们看下安卓里面状态机的具体实现,首先是状态的抽象接口IState和具体实现State。
public interface IState {
static final boolean HANDLED = true;
static final boolean NOT_HANDLED = false;
void enter();
void exit();
boolean processMessage(Message msg);
String getName();
}
public class State implements IState {
protected State() {}
@Override
public void enter() {}
@Override
public void exit() { }
@Override
public boolean processMessage(Message msg) {
return false;
}
@Override
public String getName() {
String name = getClass().getName();
int lastDollar = name.lastIndexOf('$');
return name.substring(lastDollar + 1);
}
}
状态定义了三个主要的方法,enter,exit,processMessage,状态机中的每一个状态是State的具体实现,enter/exit 等价于类的构造方法和销毁方法,processMessage方法用来处理消息,返回true即为已处理。接下来我们看下状态机StateMachine的实现;
2. 状态机初始化
StateMachine在初始化的时候创建了Looper和HandlerThread,内部维护了一个SmHandler对象,通过Handler机制来传递消息,SmHandler是消息处理派发和状态控制切换的核心,运行在单独的线程上。
mSmThread = new HandlerThread(name);
mSmThread.start();
Looper looper = mSmThread.getLooper();
mSmHandler = new SmHandler(looper, this);
接下来我们看下SmHandler这个重要的内部类。首先它提供了addState来添加状态。状态机中的每个状态使用State来封装,对于每个状态的信息又采用StateInfo来描述;
private final StateInfo addState(State state, State parent) {
if (mDbg) {
Log.d(TAG, "addStateInternal: E state=" + state.getName()
+ ",parent=" + ((parent == null) ? "" : parent.getName()));
}
StateInfo parentStateInfo = null;
if (parent != null) {
parentStateInfo = mStateInfo.get(parent);
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) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo);
return stateInfo;
}
状态添加过程其实就是为每个State创建相应的StateInfo对象,通过该对象来建立各个状态之间的关系,并以一个State-StateInfo键值对的方式保存到mStateInfo这个Hash表中,它用来保存State Machine中的所有State;
state是当前状态,parent是父状态,经过这样一系列的添加,就可以把所有状态按照树形层次结构进行组织。
sm.addState(S0);
sm.addState(S1,S0);
sm.addState(S2,S0);
sm.addState(S3,S1);
sm.addState(S4,S1);
sm.addState(S5,S2);
sm.addState(S6,S2);
sm.addState(S7,S2);
这样便生成了下图中的树。
接着我们需要设置初始状态,这个不要求是根元素
setInitialState(S4);
3. 状态机启动
当向状态机中添加完所有状态时,通过函数start来启动状态机。
public void start() {
// mSmHandler can be null if the state machine has quit.
if (mSmHandler == null) return;
mSmHandler.completeConstruction();
}
private final void completeConstruction() {
if (mDbg) Log.d(TAG, "completeConstruction: E");
//查找状态树的深度
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) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth);
//创建mStateStack,mTempStateStack状态栈
mStateStack = new StateInfo[maxDepth];
mTempStateStack = new StateInfo[maxDepth];
//设置状态堆栈
setupInitialStateStack();
/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
if (mDbg) Log.d(TAG, "completeConstruction: X");
}
在completeConstruction方法里先计算状态树的最大深度:
A. 遍历状态树中的所有节点;
B. 以每一个节点为起始点,根据节点父子关系查找到根节点;
C. 累计起始节点到根节点之间的节点个数;
D. 查找最大的节点个数,根据查找到的树的最大节点个数来创建两个状态堆栈,并调用函数setupInitialStateStack来填充该堆栈;
private final void setupInitialStateStack() {
//在mStateInfo中取得初始状态mInitialState对应的StateInfo
StateInfo curStateInfo = mStateInfo.get(mInitialState);
//从初始状态mInitialState开始根据父子关系填充mTempStateStack堆栈
for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
mTempStateStack[mTempStateStackCount] = curStateInfo;
curStateInfo = curStateInfo.parentStateInfo;
}
// Empty the StateStack
mStateStackTopIndex = -1;
//将mTempStateStack中的状态按反序方式移动到mStateStack栈中
moveTempStateStackToStateStack();
}
mStateStack和mTempStateStack是一个数组栈,用于保存状态机中的链式状态关系。
从图中可以看出当初始状态为S4时,保存到mTempStateStack的节点为:
mTempStateStack={S4,S1,S0}
mTempStateStackCount = 3;
mStateStackTopIndex = -1;
然后调用函数moveTempStateStackToStateStack将节点以反序方式保存到mStateStack中;
private final int moveTempStateStackToStateStack() {
//startingIndex= 0
int startingIndex = mStateStackTopIndex + 1;
int i = mTempStateStackCount - 1;
int j = startingIndex;
while (i >= 0) {
if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
mStateStack[j] = mTempStateStack[i];
j += 1;
i -= 1;
}
mStateStackTopIndex = j - 1;
return startingIndex;
}
mStateStack={S0,S1,S4}
mStateStackTopIndex = 2
初始化完状态栈后,SmHandler将向消息循环中发送一个SM_INIT_CMD消息;
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj))
处理该消息的方法如下;
else if (!mIsConstructionCompleted &&(mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
mIsConstructionCompleted = true;
invokeEnterMethods(0);
}
performTransitions();
消息处理过程首先调用invokeEnterMethods函数将mStateStack栈中的所有状态设置为激活状态,同时调用每一个状态的enter函数;
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName());
mStateStack[i].state.enter();
mStateStack[i].active = true;
}
}
最后调用performTransitions函数来切换状态,同时设置mIsConstructionCompleted为true,表示状态机已经启动完成,SmHandler在以后的消息处理过程中就不在重新启动状态机了。
4. 状态切换
SmHandler在处理每个消息时都会调用performTransitions来检查状态切换。
private synchronized void performTransitions() {
while (mDestState != null){
//当前状态切换了 存在于mStateStack中的State需要改变
//仍然按照链式父子关系来存储
//先从当前状态S3找到 最近的被激活的parent状态S0
//未被激活的全部保存起来(S3,S1) 返回S0
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
//将mStateStack中 不属于当前状态(S3),
//关系链上的State(S5,S2)退出(执行exit方法)
invokeExitMethods(commonStateInfo);
//将S3关系链 加入到栈中(S3,S1)
int stateStackEnteringIndex = moveTempStateStackToStateStack();
//将新加入到mStateStack中 未被激活的State激活(S3,S1)
invokeEnterMethods(stateStackEnteringIndex);
//将延迟的消息移动到消息队列的前面,以便快速得到处理
moveDeferredMessageAtFrontOfQueue();
}
}
初始时mDestState是空的,当我们调用transitionTo切换状态的时候,mDestState就会被赋值,这里只是简单地设置了mDestState变量,并未真正更新状态栈mStateStack。
private final void transitionTo(IState destState) {
mDestState = (State) destState;
}
以上图中,初始状态为S4,现在我们要切换到S7。前面介绍了保存在mStateStack数组中的节点为:
mStateStack={S0,S1,S4}
mStateStackTopIndex = 2
这是以初始状态节点为起点遍历节点树得到的节点链表。
现在要切换到S7状态节点,则以S7为起始节点,同样遍历状态节点树,查找未激活的所有节点,并保存到mTempStateStack数组中
mTempStateStack={S7,S2,S0}
mTempStateStackCount = 3
接着调用mStateStack中除S0节点外的其他所有节点的exit函数,并且将每个状态节点设置为未激活状态,因此S4,S1被设置为未激活状态;将切换后的状态节点链表mTempStateStack移动到mStateStack
mStateStack={S0,S2,S7}
mStateStackTopIndex = 2
并调用节点S2,S7的enter函数,同时设置为激活状态。
理解了整个状态切换过程后,就能更好地理解代码,首先根据目标状态建立状态节点链路表。
private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
mTempStateStackCount = 0;
StateInfo curStateInfo = mStateInfo.get(destState);
do {
mTempStateStack[mTempStateStackCount++] = curStateInfo;
if (curStateInfo != null) {
curStateInfo = curStateInfo.parentStateInfo;
}
} while ((curStateInfo != null) && !curStateInfo.active);
return curStateInfo;
}
然后弹出mStateStack中保存的原始状态;
private final void invokeExitMethods(StateInfo commonStateInfo) {
while ((mStateStackTopIndex >= 0) &&
(mStateStack[mStateStackTopIndex] != commonStateInfo)) {
State curState = mStateStack[mStateStackTopIndex].state;
if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
curState.exit();
mStateStack[mStateStackTopIndex].active = false;
mStateStackTopIndex -= 1;
}
}
将新建立的状态节点链表保存到mStateStack栈中;
private final int moveTempStateStackToStateStack() {
//startingIndex= 0
int startingIndex = mStateStackTopIndex + 1;
int i = mTempStateStackCount - 1;
int j = startingIndex;
while (i >= 0) {
if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
mStateStack[j] = mTempStateStack[i];
j += 1;
i -= 1;
}
mStateStackTopIndex = j - 1;
return startingIndex;
}
初始化入栈的所有新状态,并设置为激活状态;
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName());
mStateStack[i].state.enter();
mStateStack[i].active = true;
}
}
SmHandler在每次处理消息时都会自动更新一次mStateStack,无论mDestState变量值是否改变,所以目标状态的设置与状态栈的更新是异步的。
5. 消息处理
StateMachine处理的核心就是SmHandler,就是一个Handler,运行在单独线程中。Handler是用来异步处理派发消息,这里使用Handler管理各个状态,派发消息处理到各个状态中去执行。StateMachine提供了sendMessage来发送消息,SmHandler会接受并处理消息。
public final void sendMessage(int what) {
// mSmHandler can be null if the state machine has quit.
if (mSmHandler == null) return;
mSmHandler.sendMessage(obtainMessage(what));
}
SmHandler处理消息的过程如下;
public final void handleMessage(Message msg) {
/** Save the current message */
mMsg = msg;
if (mIsConstructionCompleted) {
//派发当前消息到state中去处理
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);
}
//消息处理完毕更新mStateStack
performTransitions();
}
变量mIsConstructionCompleted在状态机启动完成后被设置为true,因此这里将调用processMsg函数来完成消息处理。
private final void processMsg(Message msg) {
StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
//如果当前状态未处理该消息
while (!curStateInfo.state.processMessage(msg)) {
//将消息传给当前状态的父节点处理
curStateInfo = curStateInfo.parentStateInfo;
if (curStateInfo == null) {
//当前状态无父几点,则丢弃该消息
mSm.unhandledMessage(msg);
if (isQuit(msg)) {
transitionTo(mQuittingState);
}
break;
}
}
//记录处理过的消息
if (mSm.recordProcessedMessage(msg)) {
if (curStateInfo != null) {
State orgState = mStateStack[mStateStackTopIndex].state;
mProcessedMessages.add(msg, mSm.getMessageInfo(msg), curStateInfo.state,orgState);
} else {
mProcessedMessages.add(msg, mSm.getMessageInfo(msg), null, null);
}
}
}
消息处理过程是从mStateStack栈顶派发到栈底,直到该消息被处理。然后通过invokeEnterMethods调用新状态的enter方法,最后再调用performTransitions来切换状态,这个函数在上面已经分析过了。
6. 常用方法
状态机里常用的方法有以下6个:
start:用于启动状态机
addState:建立状态树
transitionTo:用于设置新状态
sendMessage:用于发送消息,然后当前状态会执行proccessMessage方法来处理消息
deferMessage:推迟消息,该消息将在下一个状态执行
三. WiFi状态机
接下来我们实际看一下WiFi工作中的状态机,WifiStateMachine通过addState构建的状态树如下;
状态比较多,每个状态的proccessMessage消息都不相同,因此在分析源码时需要看到当前是什么状态,然后再去看它的proccessMessage方法,比如处理扫描消息的主要就是DriverStartedState这个状态;
状态之间的切换是一个一个来的,不是直接跳的,比如从初始化状态到连接状态需要走过树上相关联的状态,依次执行他们的生命周期,具体的关于WiFi状态机的源码我会在下一篇文章中介绍,感谢阅读,我们下周再见。