[Android]StateMachine介绍(二)——拆解

上一篇中,我简单介绍了一下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工作逻辑的理解,可以先忽略;

LogRecords

日志相关,非核心逻辑部分,暂不深入分析;
内部维护了一个泛型为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)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值