[Android]StateMachine介绍(三)——进阶

前文回顾

这一篇会进一步介绍一些高级应用场景;

State的层级关系

State切换时的调用时序

根据之前的了解,我们知道在State切入时会调用其enter()方法,在退出时会调用其exit()方法;
但是那都是基于评级关系而言的,当State存在层级关系的时候,enter()/exit()的调用时序是否会存在差异呢?

为了探究这点,我们先假设有如下State层级关系的状态机:

在这里插入图片描述
然后,我们设置State1为初始状态,并沿着如下顺序进行状态切换:

State1 -> State2 -> State3 -> State1

其调用时序如下:
在这里插入图片描述
虚线箭头表示切换的方向,虚线旁的调用栈即为调用时序;

并列关系的状态切换规律已经理清了,那么我们接下来看看层级关系内的状态切换:

从结果来看,规律似乎是:“状态A到状态B切换时的调用栈就是从状态A回到空节点,再切到状态B的最短路线” (这里我划掉了,可见是错误的,要看结论的请直接移步文档末尾;)

State1->State2->State3/State4的现象均如此,但如果在此时,我们从State4切换到State3,就会发现不满足:

(下图省略了已经讨论完毕的State1State2,并再添加了一个State5,作为State4的子状态)

在这里插入图片描述
可以看到,具有层级关系的状态之间切换会有如下两个特征:

  • 从父状态进入子状态,直接依次向下调用子状态的enter(),直到目标状态为止;
  • 从子状态进入父状态,会从自身开始,依次向上调用exit(),直到调用目标状态的exit()后再调入目标状态的enter()

可能这里会让人很疑惑:为什么进入父状态时需要先退出再进入;

为了更好理解,这里再引入一个State6,并指定其为State3的子状态,即与State4并列:
在这里插入图片描述
其各个状态切换的调用栈如下图:

除去State4State5的状态切换情况(上面已经讨论过了),其余状态切换的调用栈如下图:

在这里插入图片描述

可见,State3下的两个子状态:State4State6,两者切换时并不会调用State3exit();因此可以确定,调用栈并不是单纯的路线顺序;

根据分析代码,我注意到一段关键代码:

private void performTransitions(State msgProcessedState, Message msg) {
	...
					/**
		             * 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);
                    // flag is cleared in invokeEnterMethods before entering the target state
                    mTransitionInProgress = true;
                    invokeExitMethods(commonStateInfo);
	...
}

这里告诉了我状态切换的一个向上查找的标准:退到非两个状态本身的公共父状态为止

两个重点:

  • 切换前后两个状态本身不能作为这里的公共父状态;
  • 找到的这个公共父状态不会退出;

举例:

  • State5 -> State4
    • State4State5的父状态,但是由于上面提到,切换前后两个状态本身不能作为公共父状态,因此还需要向上找,即State3State5State4的公共父状态;
    • 因此,调用栈为:State5.exit() -> State4.exit() -> (State3,但不会走任何回调) -> State4.enter(),与上面结论一致;
  • State5 -> State6
    • State5State6没有直接联系,因此向上寻找,可以发现最早将二者联系起来的是State3,即为公共父状态;
    • 因此,调用栈为:State5.exit() -> State4.exit() -> (State3,但不会走任何回调) -> State6.enter(),与上面结论一致;
  • State4 -> State6
    • 显而易见,State4State6的公共父状态为State3
    • 因此,调用栈为:State4.exit() -> (State3,但不会走任何回调) -> State6.enter(),与上面结论一致;

结论: 一个比较容易的记忆方法就是:

"状态A到状态B切换时的调用栈就是从状态A回到两者的公共父状态,再走到状态B的最短路线"

State切换时内部实现

上面已经介绍了现象即规律,不想深究的可以就此打住;
但是为了将状态机吃透,便于后续开发时可以有更多参考的模型,因此这里会着重介绍下这个机制实现所依赖的数据结构:

  • private StateInfo mStateStack[];
  • private int mStateStackTopIndex = -1;
  • private StateInfo mTempStateStack[];
  • private int mTempStateStackCount;

前面已经介绍过,状态切换本质是performTransitions()方法实现的,其核心实现截取如下:

            //mDestState不为null表示有外部调用transitionTo
            //此处用本地变量是因为这是在Handler内部异步执行,
            //而在接下来时间内,mDestState可能会再次改变
            State destState = mDestState;
            if (destState != null) {
				while (true) {
					//求得切换前后两个状态的公共父状态;
                    StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
                    //这个状态标记会在invokeEnterMethods中清除,在清除之前,如果发生transitionTo的调用,会通过Log.wtf打印信息;(对于eng版本就会crash)
                    mTransitionInProgress = true;

                    //从当前状态一直向上调用exit()方法,直到上方求得的公共父状态为止(不包含)
                    invokeExitMethods(commonStateInfo);
                    //更新状态栈mStateStack
                    int stateStackEnteringIndex = moveTempStateStackToStateStack();
                    //从状态栈第一个成员变量active不为true的状态开始,调用enter()方法;
                    invokeEnterMethods(stateStackEnteringIndex);

					//由于状态发生变化,因此将待处理的消息移至消息队列最前端,防止被后续消息插队导致时序错误;
                    moveDeferredMessageAtFrontOfQueue();

					//如果本地变量destState与成员变量mDestState不相等,则表示在这之前外部调用
					//transitionTo再次被调用;重新赋值destState并重复执行上述while语句;
                    if (destState != mDestState) {
                        // A new mDestState so continue looping
                        destState = mDestState;
                    } else {
                        // No change in mDestState so we're done
                        break;
                    }
                }
                //状态切换完毕,置空mDestState 
                mDestState = null;
            }

这里稍微展开一点,依次看看setupTempStateStackWithStatesToEntermoveTempStateStackToStateStack的实现:

        private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
            mTempStateStackCount = 0;
            //首先获取目标状态,并从此逆向查询
            StateInfo curStateInfo = mStateInfo.get(destState);
            do {
            	//将查询到的结果添加到mTempStateStack中
                mTempStateStack[mTempStateStackCount++] = curStateInfo;
                curStateInfo = curStateInfo.parentStateInfo;
                //直到没有父状态,或者找到第一个活跃的父状态;这里就对应上面提到的
                //“非两个状态本身的公共父状态”
            } while ((curStateInfo != null) && !curStateInfo.active);

			//因此返回的就是上面所谓的“非两个状态本身的公共父状态”
            return curStateInfo;
        }

		private final int moveTempStateStackToStateStack() {
			//简单来说,是将mTempStateStack反序录入到mStateStack中;
			//注意,mStateStackTopIndex在invokeExitMethods时已经退到公共父状态对应的下标处了,此时
			//并非指代当前状态的在mStateStack中的下标;
            int startingIndex = mStateStackTopIndex + 1;
            //因为是反序录入,因此从mTempStateStack的末尾开始;
            int i = mTempStateStackCount - 1;
            int j = startingIndex;
            while (i >= 0) {
                mStateStack[j] = mTempStateStack[i];
                j += 1;
                i -= 1;
            }

			//录入完成后的mStateStackTopIndex 应为目标状态在mStateStack中的下标;即
			//mStateStack最后一个有效元素的下标;
            mStateStackTopIndex = j - 1;
            //返回的startingIndex 代表公共父状态后的第一个子状态在mStateStack中的下标,
            //后续invokeEnterMethods方法中,会以此为起始点调用后面所有State的enter()方法,
            //完成上方《State切换时的调用时序》中讨论的调用栈;
            return startingIndex;
        }

小结:
虽然在上方 《State切换时的调用时序》 章节中,我的结论是:

"状态A到状态B切换时的调用栈就是从状态A回到两者的公共父状态,再走到状态B的最短路线"

但是从这里的代码来看,实际上逻辑并非现象看上去那么线性,具体说来是经过了这几个步骤:

  • 找到当前状态与目标状态的公共父状态;-- setupTempStateStackWithStatesToEnter()
  • 将公共父状态到目标状态的层级关系录入mTempStateStack;-- setupTempStateStackWithStatesToEnter()
  • 从当前状态开始,依次执行exit()方法,直到退到公共父状态(公共父状态不会调用exit(),即不会退出);-- invokeExitMethods()
  • 从公共父状态在mStateStack的下标为止开始,将mTempStateStack中的状态栈反序追加到mStateStack中;-- moveTempStateStackToStateStack()
  • mStateStack追加完成后,再以公共父状态在mStateStack的下标为止开始至mStateStack有效数据末尾,依次执行State的enter()方法;-- invokeEnterMethods()

后记

暂时就写这么多吧,实际上StateMachine还有很多细节可以挖掘,包括日志的记录,并发的处理,以及processMessage()方法返回值决定是否调用父状态的processMessage()等。但这些都不会影响我们对StateMachine工作机制的理解,因此这里就暂不展开了。

感兴趣的可以自行了解。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值