状态机源码分析

状态机源码

com.android.internal.util.StateMachine

状态机初始化

image.png

HandlerThread、SmHandler,Loop使用子线程loop。

状态机树状结构

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表中。除了根节点之外,每个节点状态都包含自己的parentState,从形状上来看,很像二叉树的结构,如图所示:

image.png

启动状态机

/**
 * Set the initial state. This must be invoked before
 * and messages are sent to the state machine.
 *
 * @param initialState is the state which will receive the first message.
 */
protected final void setInitialState(State initialState) {
    mSmHandler.setInitialState(initialState);
}

这个方法用来设置状态机的初始化状态,你可以理解为是个目标状态,在执行start方法后,状态机会流转到这个状态下。

public void start() {
    // mSmHandler can be null if the state machine has quit.
    if (mSmHandler == null) return;
    /** Send the complete construction message */
    mSmHandler.completeConstruction();
}

private final void completeConstruction() {
    if (mDbg) Log.d(TAG, "completeConstruction: E");
    /**
     * Determine the maximum depth of the state hierarchy
     * so we can allocate the state stacks.
     */
    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 = 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");
}

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();  
}  

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;  
}  

setupInitialStateStack方法是根据初始化状态以及树状结构关系填充mTempStateStack,可以理解为把当前状态到根节点的状态对象保存在mTempStateStack列表中。

mStateStackTopIndex表示栈顶的index,这个值会随着mStateStack的入栈和出栈发生变化,而moveTempStateStackToStateStack方法是把mTempStateStack中的状态反序填充到mStateStack中,这里只是将mStateStack不包含的新状态填充进来,并不是整体反序填充,所以要看mStateStack在填充之前的状态如何。

举个栗子:

假如目标状态为S4,则mTempStateStack内的状态依次为S4,S1,S0,若mStateStack为空,则全量反序填充,若mStateStack的栈顶为S1,则只会填充S4,并且startIndex为2。当然,初始化的时候mStateStack肯定为空,等下分析状态切换时的情况。

@Override
public final void handleMessage(Message msg) {
    if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what);
    /** Save the current message */
    mMsg = msg;
    if (mIsConstructionCompleted) {
        /** Normal path */
        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();
    if (mDbg) Log.d(TAG, "handleMessage: X");
}

当栈状态初始化完成后执行了sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)),如上段代码所示,这句话会走到第二个if条件,其实就是调用了各个状态的enter方法,并置mIsConstructionCompleted为false表示初始化完成,后续这个if条件不会再调用。

消息处理

sendMessage

StateMachine的sendMessage等一系列方法,都是用来做消息处理的,调用到processMsg方法

private final void processMsg(Message msg) {
    StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
    if (mDbg) {
        Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
    }
    if (isQuit(msg)) {
        transitionTo(mQuittingState);
    } else {
        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) {
                Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
            }
        }
        /**
         * Record that we processed the message
         */
        if (mSm.recordLogRec(msg)) {
            if (curStateInfo != null) {
                State orgState = mStateStack[mStateStackTopIndex].state;
                mLogRecords.add(msg, mSm.getLogRecString(msg), curStateInfo.state,
                        orgState);
            } else {
                mLogRecords.add(msg, mSm.getLogRecString(msg), null, null);
            }
        }
    }
}

这个方法是从当前状态向根节点循环遍历,对当前消息进行消化,如果返回true,代表当前状态已处理该消息,否则交给父状态处理。

这里有必要提到state的父类,所有的状态对象都包含重要的三个方法,分别为enter、exit和processMessage

@Override
public void enter() {
}
/* (non-Javadoc)
 * @see com.android.internal.util.IState#exit()
 */
@Override
public void exit() {
}
/* (non-Javadoc)
 * @see com.android.internal.util.IState#processMessage(android.os.Message)
 */
@Override
public boolean processMessage(Message msg) {
    return false;
}

其中前两个方法是用来做状态流转的,最后一个方法是用来做消息处理的。注意这三个方法都是在HandlerThread线程中调用的,不能直接操作ui。

状态切换

状态切换都发生在performTransitions();方法中,也就是说在每一次消息处理之后,就会进行状态的切换,当然有可能切换也有可能不切换,具体看mDestState变量。

private void performTransitions() {
    /**
     * If transitionTo has been called, exit and then enter
     * the appropriate states. We loop on this to allow
     * enter and exit methods to use transitionTo.
     */
    State destState = null;
    while (mDestState != null) {
        if (mDbg) Log.d(TAG, "handleMessage: new destination call exit");
        /**
         * Save mDestState locally and set to null
         * to know if enter/exit use transitionTo.
         */
        destState = mDestState;
        mDestState = null;
        /**
         * Determine the states to exit and enter and return the
         * common ancestor state of the enter/exit states. Then
         * invoke the exit methods then the enter methods.
         */
        StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
        invokeExitMethods(commonStateInfo);
        int stateStackEnteringIndex = moveTempStateStackToStateStack();
        invokeEnterMethods(stateStackEnteringIndex);
        /**
         * Since we have transitioned to a new state we need to have
         * any deferred messages moved to the front of the message queue
         * so they will be processed before any other messages in the
         * message queue.
         */
        moveDeferredMessageAtFrontOfQueue();
    }
    /**
     * After processing all transitions check and
     * see if the last transition was to quit or halt.
     */
    if (destState != null) {
        if (destState == mQuittingState) {
            /**
             * Call onQuitting to let subclasses cleanup.
             */
            mSm.onQuitting();
            cleanupAfterQuitting();
        } else if (destState == mHaltingState) {
            /**
             * Call onHalting() if we've transitioned to the halting
             * state. All subsequent messages will be processed in
             * in the halting state which invokes haltedProcessMessage(msg);
             */
            mSm.onHalting();
        }
    }
}

protected final void transitionTo(IState destState) {
    mSmHandler.transitionTo(destState);
}

private final void transitionTo(IState destState) {
    mDestState = (State) destState;
    if (mDbg) Log.d(TAG, "transitionTo: destState=" + mDestState.getName())
}

mDestState对象代表目标状态,正常情况下是空的,也就不会进行状态的切换,当调用transitionTo方法,代表设置了目标状态,这时候就会进入循环。这段代码的核心在这几行:

StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
invokeExitMethods(commonStateInfo);
int stateStackEnteringIndex = moveTempStateStackToStateStack();
invokeEnterMethods(stateStackEnteringIndex);

private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
    /**
     * Search up the parent list of the destination state for an active
     * state. Use a do while() loop as the destState must always be entered
     * even if it is active. This can happen if we are exiting/entering
     * the current state.
     */
    mTempStateStackCount = 0;
    StateInfo curStateInfo = mStateInfo.get(destState);
    do {
        mTempStateStack[mTempStateStackCount++] = curStateInfo;
        curStateInfo = curStateInfo.parentStateInfo;
    } while ((curStateInfo != null) && !curStateInfo.active);
    if (mDbg) {
        Log.d(TAG, "setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
              + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
    }
    return curStateInfo;
}

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;
    }
}
/**
 * Invoke the enter method starting at the entering index to top of state stack
 */
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;
    }
}

setupTempStateStackWithStatesToEnter方法是基于目标状态destState建立一个mTempStateStack列表,注意setupTempStateStackWithStatesToEnter方法的whie循环,在while循环时判断了active的状态,而且使用的do while循环,这个方法会让它找到交叉点的位置,并且包含交叉点,而如果没有交叉点,就会返回另一棵树的根节点。

举个栗子:

假设当前栈为S0,S1,S3,这时候我要切换到目标状态S4时,这个方法建立的mTempStateStack列表为S4,S1,而返回的正是S1。

这时候invokeExitMethods方法刚好倒序遍历当前mStateStack并依次退栈,直到命中交叉点为止,否则就全部退栈。

这个特性说明,每个状态的enter和exit方法只执行一次,当状态切换时,路径上已经存在并激活的状态,不会重新走exit和enter方法。

最后调用invokeEnterMethods方法,会把从交叉点到目标节点的所有状态的enter方法按顺序调用一遍。

以上就完成了这个状态机的流转过程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • StateMachine 的简单使用 步骤 源码的frameworks/base/core/java/com/an...
    JustinBetter阅读 7,137评论 3 7
  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,440评论 0 3
  • 概念 有限状态机即FSM,简称状态机,表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。 状态机可以描...
    Galileo_404阅读 10,407评论 0 8
  • 有人的地方就有故事,无论那个地方是大城市还是小山村,生活在那里的人们每天都会上演属于自己的人生。 也许生活在自己两...
    孙小山阅读 281评论 4 0
  • 南方的冬天,放眼望去,竟也是满眼绿意,要不是身上穿着厚厚的棉袄,倒也让人恍惚这究竟是冬天,还是春天。
    神喵君阅读 169评论 0 0