相关文章推荐
威武的南瓜  ·  [Android]StateMachine介 ...·  5 月前    · 
威武的南瓜  ·  Top 5 typeorm Code ...·  11 月前    · 
聪明伶俐的课本  ·  SpringBoot ...·  4 分钟前    · 
爱看球的牙膏  ·  IDEA ...·  5 分钟前    · 
帅气的红茶  ·  清华大学出版社·  2 小时前    · 

上一篇 中,我简单介绍了一下StateMachine的工作原理,并尝试用示意图的方式描述其运行机制。

这一篇会从源码入手,对StateMachine进行一个详细的拆解;

注意,如下所有代码片段均非源代码直接截取,而是经过了各种删减的,删减部分包括但不限于日志输出部分、不影响代码逻辑分析的分支判断等;同时,为了方便阅读、理解,对原有的英文注释也进行了删减,并添加了部分中文的,基于个人的理解;因此请勿直接使用;

类与内部类

State

首先需要介绍一下State这个类;

前面提到,StateMachine是处理状态与消息两类事物的工具类,消息通过Message表征,那么状态就需要借助State类来表达了:

这个由State.java提供支持,其继承自IState,并完成了如下几个抽象方法的空实现:

    //当状态机切换到该状态时调用
    public void enter();
    //当状态机切离该状态时调用
    public void exit();
     * 当状态机切换到该状态后调用,
     * 返回true表示已经处理完所有事件;
     * 返回false表示该状态没有处理任何信息;
     * 状态机会根据其返回值决定下一步的处理逻辑,这里先不赘述,下面
     * 会讲到
    @Override
    public boolean processMessage(Message msg) {
        return false;

并完成了toString()方法的重写:

    @Override
    public String getName() {
        String name = getClass().getName();
        int lastDollar = name.lastIndexOf('$');
        return name.substring(lastDollar + 1);

显然,要使用时需要继承State并重写对应方法以满足需求;

StateMachine

StateMachine本质上是的“半成品”,具体使用时需要继承并重写部分空实现,此处仅分析StateMachine.java的逻辑;
StateMachine的构造方法有一下几个:

    protected StateMachine(String name) {
        mSmThread = new HandlerThread(name);
        mSmThread.start();
        Looper looper = mSmThread.getLooper();
        initStateMachine(name, looper);
    protected StateMachine(String name, Looper looper) {
        initStateMachine(name, looper);
    protected StateMachine(String name, Handler handler) {
        initStateMachine(name, handler.getLooper());

默认有3个构造方法,参数1始终为字符串name,参数2可以为Handler/Looper,或者没有参数2;

  1. 参数1会直接传递给initStateMachine方法赋值给mName;
  2. 参数2会转化为Looper对象传递给initStateMachine方法用于构造mSmHandler;
  3. 如果没有参数2,则通过创建一个HandlerThread,并将其对应Looper获取到,再传递给initStateMachine方法用于构造mSmHandler;

在构造方法中,均会调用到initStateMachine方法:

    private SmHandler mSmHandler;
    private HandlerThread mSmThread;
    private void initStateMachine(String name, Looper looper) {
        mName = name;
        mSmHandler = new SmHandler(looper, this);

initStateMachine中会构造出StateMachine中大部分逻辑实现的SmHandler对象;

SmHandler

继承自Handler,也是StateMachine主要依赖的机制,其构造方法如下:

    //内部维护的一个表征“正在挂起”状态的State子类对象
    private HaltingState mHaltingState = new HaltingState();
    //内部维护的一个表征“正在退出”状态的State子类对象
    private QuittingState mQuittingState = new QuittingState();
    //内部类实例对象对外部StateMachine类实例的引用,用于进行各种回调
    private StateMachine mSm;
     * 构造方法,参数1为Looper,用于确定该Handler运行的线程;
     * 参数2为StateMachine的实例对象,用于存到成员变量中,
     * 便于后续进行各种回调;
    private SmHandler(Looper looper, StateMachine sm) {
        super(looper);
        mSm = sm;
        //添加两个必然需要的状态:正在挂起、正在退出
        addState(mHaltingState, null);
        addState(mQuittingState, null);
     * addState实现,参数1表示需要添加的状态,参数2指定该状态
     * 关联的父类状态,如果该状态独立存在,则参数2为null
    private final StateInfo addState(State state, State parent) {
        StateInfo parentStateInfo = null;
        if (parent != null) {
            parentStateInfo = mStateInfo.get(parent);
            if (parentStateInfo == null) {
                //如果parent也没有通过addState添加过,则先递归调用,添加parent
                parentStateInfo = addState(parent, null);
        StateInfo stateInfo = mStateInfo.get(state);
        if (stateInfo == null) {
            //该状态没有添加过,则构造StateInfo对象将其封装保存;
            stateInfo = new StateInfo();
            //添加到HashMap<State, StateInfo> mStateInfo中
            mStateInfo.put(state, stateInfo);
        if ((stateInfo.parentStateInfo != null)
                && (stateInfo.parentStateInfo != parentStateInfo)) {
            //如果待添加的State已经存在于其他parent下,则抛出异常
            throw new RuntimeException("state already added");
        stateInfo.state = state;
        stateInfo.parentStateInfo = parentStateInfo;
        stateInfo.active = false;
        //返回封装好的StateInfo对象
        return stateInfo;

在StateMachine调用start方法后,会调用SmHandler内部的completeConstruction方法进行状态初始化:

    private final void completeConstruction() {
        int maxDepth = 0;
        //遍历所有已经通过addState添加的状态,并获取所有层级关系中最复杂的一个对应的深度
        for (StateInfo si : mStateInfo.values()) {
            int depth = 0;
            for (StateInfo i = si; i != null; depth++) {
                i = i.parentStateInfo;
            if (maxDepth < depth) {
                maxDepth = depth;
        //以最大深度作为数组长度创建数组,避免数组下标越界,同时减少扩容带来的开销;
        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));
    private final void setupInitialStateStack() {
    	//mInitialState通过StateMachine.setInitialState()设置
        StateInfo curStateInfo = mStateInfo.get(mInitialState);
        //这里旨在将mInitialState按照自下往上的先后顺序,添加到mTempStateStack中
        for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
            mTempStateStack[mTempStateStackCount] = curStateInfo;
            curStateInfo = curStateInfo.parentStateInfo;
        mStateStackTopIndex = -1;
		//最后再将整个mTempStateStack的内容反序添加到mStateStack中
        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;

在初始化完成后,StateMachine就运行起来了,此时只需要按照需求进行对应方法调用即可;

前面提到,StateMachine主要是处理状态切换、消息通知两个方法,前者通过transitionTo实现,后者通过sendMessage及其类似方法完成,并调用SmHandler的对应方法通知到SmHandler中;

而作为Handler的一个子类,SmHandler处理消息是在handleMessage方法中:

    public final void handleMessage(Message msg) {
        if (!mHasQuit) {
            if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
                 * 除去StateMachine start/stop调用时发送的Message,
                 * 其余所有Message均会在mSm不为null时
                 * handleMessage初期回调StateMachine的onPreHandleMessage方法
                 *(默认空实现,可按需继承后重写);
                mSm.onPreHandleMessage(msg);
            //保存为成员变量
            mMsg = msg;
            //根据msg确认对应的State
            State msgProcessedState = null;
            if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
                //初始化后所有调用发送的Message都会走这个逻辑分支
                msgProcessedState = processMsg(msg);
            } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
                    && (mMsg.obj == mSmHandlerObj)) {
                //初始化时首次执行的逻辑分支
                mIsConstructionCompleted = true;
                invokeEnterMethods(0);
            } else {
                //异常消息
                throw new RuntimeException("StateMachine.handleMessage: "
                        + "The start method not called, received msg: " + msg);
            //进行状态切换
            performTransitions(msgProcessedState, msg);
            if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
                 * 除去StateMachine start/stop调用时发送的Message,
                 * 其余所有Message均会在mSm不为null时
                 * handleMessage初期回调StateMachine的onPostHandleMessage方法
                 *(默认空实现,可按需继承后重写);
                mSm.onPostHandleMessage(msg);

这里为了理解方便,暂时先不讨论不同State的层级关系(hierarchy),我计划在下一篇中介绍
通过阅读SmHandler的handleMessage方法实现,我们可以得出:

  • 真正的状态切换,是在performTransitions方法中完成的,并包含了上一状态的exit回调以及新状态的enter回调;
  • StateMachine的transitionTo方法本身只是将目标状态赋值给了mDestState,没有实际完成状态的切换,也就是不会立即调用enter;
  • 要想触发上一状态的exit与新状态的enter回调,必须在transitionTo调用后再通过sendMessage(或类似方法)发送一条消息给到SmHandler,通过handleMessage方法来完成状态的切换与相关回调;

SmHandler.StateInfo

这是一个SmHandler的内部类,用于表征一个State的所属关系;

    private class StateInfo {
		//StateInfo表征的State对象
		State state;
		//该State的父节点
        StateInfo parentStateInfo;
		//是否活跃,当StateMachine切换到该状态时为true,切离时改为false
		//即:调用对应的State的enter方法后改为true,exit方法后为false
        boolean active;
        @Override
        public String toString() {
            return "state=" + state.getName() + ",active=" + active + ",parent="
                    + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());

LogRec

日志相关,非核心逻辑部分,暂不深入分析;
用于表示单条状态切换记录;主要记录了如下信息:

    //状态机的引用
    private StateMachine mSm;
    //构造、更新的时间戳
    private long mTime;
    //取自update方法时传入的Message对象的what变量,可用于表征状态
    private int mWhat;
    /* 取自update方法传入的String类型参数
     * 默认由StateMachine的getLogRecString方法提供
     * 默认返回空串;
    private String mInfo;
    /* 取自update方法传入的第一个IState类型参数
     * 默认由StateMachine的getLogRecString方法提供
     * 默认返回空串;
    private IState mState;
    /* 取自update方法传入的第二个IState类型参数
     * 与调用时的 mStateStack[mStateStackTopIndex].state变量信息一致;
     * 表示状态切换前的那个状态;
    private IState mOrgState;
    /* 取自update方法传入的第三个IState类型参数
     * 与调用时的mDestState变量信息一致;
    private IState mDstState;

这里关于mState/mOrgState/mDstState三个变量的关系,会在下一篇介绍State的层级关系(hierarchy)时展开,这里不影响对整个StateMachine工作逻辑的理解,可以先忽略;

日志相关,非核心逻辑部分,暂不深入分析;
内部维护了一个泛型为LogRec的向量(Vector),用于记录所有的LogRec记录,默认最大容量为20,可通过调用StateMachine的setLogRecSize方法修改容量;

主要的常用方法

    //用于添加新的State
    public final void addState(State state, State parent);
    public final void addState(State state);
    //用于删除State
    public final void removeState(State state);
    //用于切换State
    public final void transitionTo(IState destState);
    //用于直接切换到“正在挂起”状态
    public final void transitionToHaltingState();
    //用于调用completeConstruction方法进行状态初始化,并向SmHandler中发送SM_INIT_CMD方法进行初始化;handleMessage在识别到SM_INIT_CMD消息时,会调用invokeEnterMethods方法,逐一调用所有已经添加的State的enter方法;
    public void start();
    //与SmHandler(Handler)的调用一致,其内部实现也是调用mSmHandler的对应重载方法
    public final Message obtainMessage();(包括重载方法)
    public void sendMessage(int what);
    public void sendMessage(Message msg);
    public void sendMessageDelayed(int what, int arg1, long delayMillis);(包括重载方法)
    protected final void sendMessageAtFrontOfQueue(int what);(包括重载方法)
    protected final void removeMessages(int what);(包括重载方法)
    public final void deferMessage(Message msg);
    protected final void removeDeferredMessages(int what);
    //通知SmHandler发送SM_QUIT_CMD消息,并在handleMessage时调用到transitionTo(mQuittingState)与cleanupAfterQuitting(),后者会停止Looper,并释放所有引用,清空所有数据,至此,该状态机不再可用。后续只能通过构造新的实例对象(调用start方法也无法重新启用)
    public final void quit();
	//与quit()的区别仅在于sendMessage与sendMessageAtFrontOfQueue的区别;
    public final void quitNow();
    //通过SmHandler设置初始状态,并将其赋值给SmHandler内部的成员变量mInitialState
    public final void setInitialState(State initialState);

在调用StateMachine的start方法之前,至少需要完成如下调用:

public final void setInitialState(State initialState);
public final void addState(State state, State parent);//或其他重载方法

如果不调用setInitialState,mInitialState在start后依旧为null,SM_INIT_CMD走到performTransitions()时mStateStackTopIndex仍然为-1,导致数组下标越界;
此外,由于setInitialState的参数必须是已经通过addState添加的State(否则mStateInfo中不会有对应的StateInfo),setupInitialStateStack方法时也不会向mStateStack中添加有效元素,因此mStateStackTopIndex依旧为01,同样会导致数组下标越界;

这篇是从源码结构拆分的角度进行的代码分析,比较零散,但方便有各种需求的人查阅;
接下来会计划再写一篇,着重介绍下State的层级关系(hierarchy)

在上一篇中,我简单介绍了一下StateMachine的工作原理,并尝试用示意图的方式描述其运行机制。这一篇会从源码入手,对StateMachine进行一个详细的拆解;注意,如下所有代码片段均非源代码直接截取,而是经过了各种删减的,删减部分包括但不限于日志输出部分、不影响代码逻辑分析的分支判断等;同时,为了方便阅读、理解,对原有的英文注释也进行了删减,并添加了部分中文的,基于个人的理解;因此请勿直接使用;类与内部类State首先需要介绍一下State这个类;前面提到,StateMachine是处理
Android frameworks源码StateMachine使用举例及源码解析 工作中有一同事说到Android状态机StateMachine。作为一名Android资深工程师,我居然没有听说过StateMachine,因此抓紧时间学习一下。 StateMachine不是Android SDK中的相关API,其存在于frameworks层源码中的一个Java类。可能因为如此,许多应用层的开发人员并未使用过。 因此这里我们先说一下StateMachine的使用方式,然后再对源码进行相关介绍StateMachine使用举例 StateMachine原理学习 一、StateMachine使用
StateMachine 的简单使用步骤 源码的frameworks/base/core/java/com/android/internal/util里面把StateMachine.java 、State.java 、IState.java复制到project目录中 自定义StateMachine类继承StateMachine 自定义状态State继承State:重写enter、processMs
最近在公司做一个Spring Boot表单项目,表单涉及的状态如图所示: 在设计表单状态转换模块时,想到状态机这个概念。在网上检索相关的实现框架,发现Spring StateMachine框架。网上大多数的教程都是非常简单的Demo,只有一个状态机连续切换的示例,很难作为一个实战入门的Demo。幸运的是在网上找到了一个Spring系列的视频,其中涉及到了状态机的实战项目。这篇文章也是基于该视频教程,结合自己的项目实践做一个完整的状态机实战笔记。 StateMachine实战 Spring Sta
StateMachine介绍 StateMachine,中文名状态机。顾名思义,是用于记录、切换状态的。除此之外,StateMachine还可以通过预定义执行的方法,使其在状态切换开始前、过程中、结束后分别执行不同的逻辑; 接下来就对StateMachine进行一个简单的拆解; 注意,如下所有代码片段均不是源代码截取,而是经过了各种删减的,删减包括但不限于日志输出部分、不影响代码逻辑分析的分支判断等;同时,为了方便阅读、理解,对原有的英文注释也进行了删减,并添加了部分中文的,基于个人的理解; 因此请勿直接使
持久化到内存hashmap中 实现StateMachinePersist接口,并通过实现write和read方法,然后构造DefaultStateMachinePersister bean FSMStateMachinePersist 实现接口 StateMachinePersist package com.wtx.springboot2.statemachine; import org.springframework.statemachine.StateMachineContext; impor
State切换时的调用时序 根据之前的了解,我们知道在State切入时会调用其enter()方法,在退出时会调用其exit()方法; 但是那都是基于评级关系而言的,当State存在层级关系的时候,enter()/exit()的调用时序是否会存在差异呢? 为了探究这点,我们先假设有如下State层级关系的状态机: [Android][踩坑]gradle中配置android.useAndroidX与android.enableJetifier使应用对support库的依赖自动转换为androidx的依赖 30990 Android Studio Giraffe引用framework.jar方法 https://blog.csdn.net/qq_15050521/article/details/134150872 楼主可以更新到文章中
Ubuntu手动安装gnome-shell扩展组件 Utopia_HAO: 本地安装组件g步不太懂,但是重启好了。你是我的神!!! Ubuntu 22.04系统搭建环境编译AOSP P ftnkn: fei chang gan xie, kun rao le wo hao jiu de wen ti jie jue le [WSL2]WSL2迁移虚拟磁盘文件ext4.vhdx m0_74125121: 有多种分发也是一样的操作吗?

LogRecords

 
推荐文章