演示效果:

android 状态机简介 android 状态机实现_状态机


需求描述: 在发送礼物按钮上进行连击,界面上展示礼物且礼物数字随着连击事件增加。需求扩展:数字变动的间隔时间可通过参数设置。

思路简析: 这可以看作是一个生产-消费模型,用队列来存储连击事件。什么是状态机思想呢?简单地说,就是n个状态在不同条件下互相转化的过程。那么如何通过状态机思想来分析这个需求呢?

首先,罗列出所有状态:STARTWORKINGWAITINGSTOP

然后画草图,画出状态之间的转换条件:

android 状态机简介 android 状态机实现_android 状态机简介_02


按照状态机思想画上草图后,状态之间的关系链就会变得很清晰。

源码:

public class ComboUtil<T extends ComboUtil.TimeEvt> {
    //点击事件队列
    private LinkedList<T> queue;
    private ComboHandler<T> mHandler;

    /**
     * @param noRspTime       无响应时间 两次点击间隔noRspTime时默认为新一轮连击
     * @param takeEvtInterval 取队列数据的间隔时间,同一轮连击里点击事件的间隔时间
     */
    public ComboUtil(Callback<T> callback, long noRspTime, long takeEvtInterval) {
        queue = new LinkedList<>();
     
        mHandler = new ComboHandler<T>(callback, noRspTime, takeEvtInterval) {

            @Override
            public T takeEvt() {
                return queue.isEmpty() ? null : queue.removeFirst();
            }

            @Override
            public T getEvt() {
                return queue.isEmpty() ? null : queue.getFirst();
            }

            @Override
            public boolean isEmptyQueue() {
                return queue.isEmpty();
            }
        };
    }

    public void onClick(T evt) {
        queue.add(evt);
        Log.d("连击测试", "enqueue");

        mHandler.onModelEnqueue();
    }

    public void onDestroy() {
        mHandler.onDestroy();
        queue.clear();
    }

    public static class TimeEvt {
        private long mills;

        public TimeEvt() {
            this.mills = System.currentTimeMillis();
        }

        public long getMills() {
            return mills;
        }
    }

    public interface Callback<T> {
        //连击准备
        void onStart();

        //连击回调
        void onCombo(T evt);
      
        //等待点击
        void onWaiting();

        //连击结束
        void onStop();
    }

    public static abstract class ComboHandler<T> extends Handler {
        public static final int START = 1;
        public static final int WORKING = 2;
        public static final int STOP = 4;
        public static final int WAITING = 3;
        private ComboUtil.Callback<T> callback;
        private long takeEvtInterval;
        private long noRspTime;//单位:毫秒

        public ComboHandler(ComboUtil.Callback<T> callback, long noRspTime, long takeEvtInterval) {
            this.callback = callback;
            this.takeEvtInterval = takeEvtInterval;
            this.noRspTime = noRspTime;
        }

        @Override
        public void handleMessage(Message msg) {
            //用msg.arg1表示上一个状态
            int from = msg.arg1;

            Message newMsg = Message.obtain();

            switch (msg.what) {
                case START:
                    if (from < STOP && from >= START) return;//不符合DFA状态机算法图

                    Log.d("连击测试", "receive START msg");

                    newMsg.arg1 = START;

                    if (callback != null)
                        callback.onStart();

                    if (isEmptyQueue()) {
                        newMsg.what = WAITING;//队列为空进入等待状态
                    } else {
                        newMsg.what = WORKING;//进入工作状态
                    }

                    sendMessage(newMsg);
                    break;

                case WORKING:
                    if (from == WORKING || from == WAITING || from == START) {
                        //符合DFA状态机算法图
                        Log.d("连击测试", "receive WORKING msg");

                        newMsg.arg1 = WORKING;

                        //从队尾取出一个点击事件
                        T t = takeEvt();
                        ComboUtil.TimeEvt evt = (TimeEvt) t;

                        if (evt == null) {
                            //当前队列已空
                            if (from == WAITING) {
                                //上一个状态已经是等待状态了
                                newMsg.what = STOP;
                                sendMessage(newMsg);//直接结束本次连击

                            } else {
                                //进入等待状态
                                newMsg.what = WAITING;
                                sendMessage(newMsg);
                            }

                        } else {
                            if (callback != null)
                                callback.onCombo(t);

                            //判断下一条数据时间
                            ComboUtil.TimeEvt nxtEvt = (TimeEvt) getEvt();
                            if (nxtEvt != null && nxtEvt.getMills() - evt.getMills() >= noRspTime) {
                                //认为本次连击结束,并重新开始新一轮连击
                                newMsg.what = STOP;
                            } else newMsg.what = WORKING;

                            sendMessageDelayed(newMsg, takeEvtInterval);
                        }
                    }
                    break;

                case WAITING:
                    if (from == START || from == WORKING || from == WAITING) {
                        Log.d("连击测试", "receive WAITING msg");

                        newMsg.arg1 = WAITING;

                        boolean isEmpty = isEmptyQueue();

                        //符合DFA状态机算法图
                        if (callback != null)
                            callback.onWaiting();

                        switch (from) {
                            case START:
                            case WORKING:
                                //取数据时发现队列已空,进入等待状态,noRspTime时间后再次判断队列是否为空
                                newMsg.what = WAITING;
                                sendMessageDelayed(newMsg, noRspTime);
                                break;

                            case WAITING:
                                if (isEmpty) {
                                    //经过noRspTime后 数据队列仍然为空,直接发送stop msg 结束这次连击
                                    newMsg.what = STOP;
                                } else {
                                    //经过noRspTime后 数据队列已入队新的数据,发送take msg 继续取数据
                                    newMsg.what = WORKING;
                                }
                                sendMessage(newMsg);
                                break;
                        }
                    }
                    break;

                case STOP:
                    if (from == WORKING || from == WAITING) {
                        Log.d("连击测试", "receive STOP msg");

                        newMsg.arg1 = STOP;

                        if (callback != null)
                            callback.onStop();

                        //发送stop msg到接收stop msg之间可能还有入队操作,需要再检查一次队列
                        if (!isEmptyQueue()) {
                            newMsg.what = START;
                            sendMessage(newMsg);
                        }
                    }
                    break;
            }
        }

        //读写操作
        public abstract T takeEvt();

        //只读操作
        public abstract T getEvt();

        public abstract boolean isEmptyQueue();

        //是否是空闲状态
        public boolean isIDLE() {
            boolean hasMsg = hasMessages(START) || hasMessages(WORKING) || hasMessages(WAITING) || hasMessages(STOP);
            return !hasMsg;
        }

        /**
         * 当前空闲 则发送start msg 开始工作
         * 开始工作后会自动按照逻辑从数据model队列取数据
         */
        public void onModelEnqueue() {
            if (isIDLE())
                sendEmptyMessage(START);
            else if (hasMessages(WAITING)) {
                //如果是等待状态-唤醒
                removeMessages(WAITING);
                Message newMsg = Message.obtain();
                newMsg.arg1 = WAITING;
                newMsg.what = WORKING;
                sendMessage(newMsg);
            }
        }

        public void onDestroy() {
            removeCallbacksAndMessages(null);

            if (callback != null)
                callback.onStop();
        }
    }
}

大家有什么好用的制图软件推荐吗?

有任何疑问或建议欢迎留言讨论!