IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> [Android]StateMachine介绍(三)——进阶 -> 正文阅读

[移动开发][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工作机制的理解,因此这里就暂不展开了。

感兴趣的可以自行了解。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-07 00:02:59  更:2021-07-07 00:04:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/5 15:32:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码