Android StateMachine

前言

最近在了解WIFI模块时,发现WifiController类继承至StateMachine,而WifiController是WIFI状态切换的重要类,了解StateMachine的工作流程是很有必要的。
StateMachine在状态机的类别中属于有限状态机(Finite state machine),简称FSM,属于状态设计模式中Context环境类,适用于需要在复杂状态与业务之间进行切换的场景,如游戏当中人物的走、跑、攻击,会经常在这几个状态之中进行切换,运用状态机能保证项目的可拓展性,提高可读性。

基本使用

首先声明SDK当中StateMachine的包路径为:
com.android.internal.util.StateMachine
且该类是被标注为隐藏类,我们是无法直接使用的,为了能体验下状态机的使用,我们需要将其相关类(IState、State、StateMachine)直接复制到我们的测试Demo中,当然复制完成需要稍微调整下,不然编译不通过,如下所示:


将SDK中的源码复制到项目中

StateMachine的基本使用必须按如下四个步骤进行,缺一不可:

  1. 继承StateMachine,StateMachine类的构造函数是Protect访问权限,所以只能通过继承实现实例化
  2. 通过addState方法构造状态层次结构(树形结构,可多棵),状态层次结构根据状态转移图构建,各种状态需要继承State类,实现自己相应业务逻辑
  3. 通过setInitialState设置初始状态
  4. 调用start方法启动状态机

其他常用API如下表所示:

Method Description
quit() 停止状态机,会进入QuttingState
sendMessage(Message msg) 发送一个消息,供各状态处理
deferMessage(Message msg) 发送一个延迟消息,在下一次状态转换时,才会被放入消息队列
transitionTo(IState state) 转移至相应状态
transitionToHaltingState() 进入HaltingState
/**
 * Created by graymonkey on 18-1-6.
 * StateMachine类的构造函数是Protect访问权限,所以只能通过继承实现实例化
 */

public class TestStateMachine extends StateMachine {
    public TestStateMachine(String name) {
        super(name);
        constructStatesHierarchy();
    }
    /**
     * 构造状态层次结构(树形结构,可多棵)
     */
    private void constructStatesHierarchy(){
 //step 2
        //构造第一棵树形层次结构
        State s1 = new S1();
        State s2 = new S2();
        State p1 = new P1();
        addState(s1,p1);
        addState(s2,p1);
        //构造第二棵树形层次结构
        State p2 = new P2();
        addState(p2);
 //step 3
        setInitialState(s1);
 //step 4
        start();
    }

}

经过上面的代码,则该状态机的状态层次结构如下图所示,关于多出来的HaltingState、QuittingState见后文分析。

Demo状态机层次结构

关于StateMachine的基本使用在SDK中首部注释中已经说的很详细了,下面直接进入源码看看其实现,同时捋一捋工作流程。
Tips:Demo需要在API24以上才能运行
Demo下载地址

源码分析—初始化准备

StateMachine构造函数
 /**
     * Constructor creates a StateMachine with its own thread.
     *
     * @param name of the state machine
     */
    protected StateMachine(String name) {
        mSmThread = new HandlerThread(name);
        mSmThread.start();
        Looper looper = mSmThread.getLooper();

        initStateMachine(name, looper);
    }

 /**
     * Initialize.
     *
     * @param looper for this state machine
     * @param name of the state machine
     */
    private void initStateMachine(String name, Looper looper) {
        mName = name;
        mSmHandler = new SmHandler(looper, this);
    }

从构造函数来看,StateMachine内部开启了一个HandlerThread,并且通过SmHandler发送消息给HandlerThread。

SmHanlder
 private static class SmHandler extends Handler {
  ......
  /** Stack used to manage the current hierarchy of states */
        //由mTempStateStack进行逆序反向操作得到
        private StateInfo mStateStack[];

        /** Top of mStateStack */
        private int mStateStackTopIndex = -1;

        /** A temporary stack used to manage the state stack */
        private StateInfo mTempStateStack[];

        /** The top of the mTempStateStack */
        private int mTempStateStackCount;

        /** State used when state machine is halted */
        //空闲状态,当其他State都处理完毕,就会进入该状态
        private HaltingState mHaltingState = new HaltingState();

        /** State used when state machine is quitting */
        //退出状态,停用状态机进入的状态
        private QuittingState mQuittingState = new QuittingState();

        /** Reference to the StateMachine */
        private StateMachine mSm;
      
       /** The map of all of the states in the state machine */
        //用一个HashMap来存储状态机的状态层次结构
        private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();

        /** The initial state that will process the first message */
        private State mInitialState;

        /** The destination state when transitionTo has been invoked */
        private State mDestState;
    ......
}

以上代码只是列出了SmHandler的关键成员变量,SmHandler属于StateMachine的私有静态内部类,其中StateInfo、HaltingState、 QuittingState属于SmHandler的私有内部类,留意加了中文注释的成员变量,后文会用到。

SmHandler构造函数
      /**
         * Constructor
         *
         * @param looper for dispatching messages
         * @param sm the hierarchical state machine
         */
        private SmHandler(Looper looper, StateMachine sm) {
            super(looper);
            mSm = sm;
            //添加空闲状态与退出状态
            addState(mHaltingState, null);
            addState(mQuittingState, null);
        }

可见,在SmHandler实例化的时候,就会向其哈希状态表mStateInfo当中添加空闲状态与退出状态,这就是前文多出来的HaltingState、QuttingState的原因。
注意:此处addState方法并非调用的是StateMachine的addState方法,而是SmHandler自己的方法,实际上StateMachine的addState方法调用也是SmHandler的addState方法。

//StateMachine类的addState方法
    /**
     * Add a new state to the state machine
     * @param state the state to add
     * @param parent the parent of state
     */
    public final void addState(State state, State parent) {
        mSmHandler.addState(state, parent);
    }

下面进入SmHandler#addState方法看看

       /**
         * Add a new state to the state machine. Bottom up addition
         * of states is allowed but the same state may only exist
         * in one hierarchy.
         *允许自下向上添加,即添加一个State可以同时添加起父状态,但是同一个
         *状态不能同时拥有2个父状态,会抛出异常
         *
         * @param state the state to add
         * @param parent the parent of state
         * @return stateInfo for this state
         */
        private final StateInfo addState(State state, State parent) {
            if (mDbg) {
                mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
                        + ((parent == null) ? "" : parent.getName()));
            }
            StateInfo parentStateInfo = null;
            if (parent != null) {
                parentStateInfo = mStateInfo.get(parent);
                if (parentStateInfo == null) {
                    // Recursively add our parent as it's not been added yet.
                    parentStateInfo = addState(parent, null);
                }
            }
            StateInfo stateInfo = mStateInfo.get(state);
            if (stateInfo == null) {
                stateInfo = new StateInfo();
                mStateInfo.put(state, stateInfo);
            }

      // Validate that we aren't adding the same state in two different hierarchies.
      //异常校验,如果一个State已经有一个父状态,
      //再添加一个父状态则会抛出异常
            if ((stateInfo.parentStateInfo != null)
                    && (stateInfo.parentStateInfo != parentStateInfo)) {
                throw new RuntimeException("state already added");
            }
          //留意,StateInfo的关键数据结构,后续会用到
            //当前状态
            stateInfo.state = state;
            //当前状态的父节点信息StateInfo类型
            stateInfo.parentStateInfo = parentStateInfo;
            //当前状态是否激活,调用State#enter方法后会激活置为true
            stateInfo.active = false;
            if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
            return stateInfo;
        }

该方法主要是向HashMap<State,StateInfo> mStateInfo添加元素,StateInfo当中存有当前节点的父节点信息,通过Key-Value组合来表示状态机的状态层级结构。
注意:如果一个State已经拥有了父节点(State类型),再添加另一个新的父节点程序会抛出异常,如果需要更换父节点需要先解绑(置空)之前的父节点再添加新的父节点,错误层级结构如下图所述。

错误的层次结构

看完了addState方法,紧跟上文的基本使用步骤,我们来看看StateMachine#setInitialState方法,同addState方法一样,实际上调用也是SmHandler#setInitialState方法,直接进入SmHandler# setInitialState方法

  /** @see StateMachine#setInitialState(State) */
        private final void setInitialState(State initialState) {
            if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
            mInitialState = initialState;
        }

逻辑很简单,就是对SmHandler的成员变量mInitialState进行赋值操作。
小结:至此,已经分析完基本使用步骤前三步骤的源码,这三个步骤的目的很明显就是对状态机进行一些初始化工作:

  1. 通过addState函数初始化状态机的状态层次结构,该层次结构由SmHandler中的HashMap<State,StateInfo> mStateInfo来存储表示。
  2. 通过setInitialState方法设置初始状态

源码分析—工作流程

紧跟上文基本使用步骤,我们进入第四步StateMachine#start方法,来了解状态机的工作流程。

     * Start the state machine.
     */
    public void start() {
        // mSmHandler can be null if the state machine has quit.
        SmHandler smh = mSmHandler;
        if (smh == null) return;

        /** Send the complete construction message */
        smh.completeConstruction();
    }

进入SmHandler#completeConstruction

   /**
         * Complete the construction of the state machine.
         */
        private final void completeConstruction() {
            if (mDbg) mSm.log("completeConstruction: E");

            /**
             * Determine the maximum depth of the state hierarchy
             * so we can allocate the state stacks.
             *回溯遍历所有State节点,得到最大的树形结构深度
             */
            int maxDepth = 0;
            for (StateInfo si : mStateInfo.values()) {
                int depth = 0;
                for (StateInfo i = si; i != null; depth++) {
                    i = i.parentStateInfo;
                }
                if (maxDepth < depth) {
                    maxDepth = depth;
                }
            }
            if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
            //初始化状态栈
            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));

            if (mDbg) mSm.log("completeConstruction: X");
        }

我们Demo的树状层级结构的深度应该为2,第一棵树的深度最大,下面进入setupInitialStateStack()看看是如何初始化状态栈的。

  /**
         * Initialize StateStack to mInitialState.
         */
        private final void setupInitialStateStack() {
            if (mDbg) {
                mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
            }

            StateInfo curStateInfo = mStateInfo.get(mInitialState);
            for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
                mTempStateStack[mTempStateStackCount] = curStateInfo;
                curStateInfo = curStateInfo.parentStateInfo;
            }

            //逻辑上清空mStateStack,通过将栈顶Index设置为-
            mStateStackTopIndex = -1;
            //初始化 mStateStack,就是将mTempStateStack逆序赋值给mStateStack
            moveTempStateStackToStateStack();
        }

 /**
         * Move the contents of the temporary stack to the state stack
         * reversing the order of the items on the temporary stack as
         * they are moved.
         *从mStateStack的当前的栈顶index开始,添加mTempStateStack的出栈元素
         * @return index into mStateStack where entering needs to start
         */
        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;
        }

可见先初始化mTempStateStack,根据我们之前设置的初始化状态往上回溯直至根节点,即初始化状态(StateInfo类型)先入栈,然后是其父节点、祖父节点入栈,以此类推直到根节点入栈;接着通过moveTempStateStackToStateStack()方法初始化mStateStack,即mTempStateStack初始状态位于栈底,mStateStack初始状态位于栈顶。以我们的Demo状态层级结构为例,此时2个状态栈的内容应该如下:


状态栈

接下来通过SmHandler#sendMessageAtFrontOfQueue发送SM_INIT_CMD消息,我们进入SmHandler#handleMessage方法看看是如何处理的。

 /**
         * Handle messages sent to the state machine by calling
         * the current state's processMessage. It also handles
         * the enter/exit calls and placing any deferred messages
         * back onto the queue when transitioning to a new state.
         */
        @Override
        public final void handleMessage(Message msg) {
            if (!mHasQuit) {
       //如果收到的消息既不是初始化也不是退出,那么就会调用StateMachine的onPreHandleMessage方法,
       //进行消息预处理
                if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
                    mSm.onPreHandleMessage(msg);
                }

                if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);

                /** Save the current message */
                mMsg = msg;

                /** State that processed the message */
                /**我们通过start方法首次进入肯定是走else if分支,完成初始化命令
                后续收到消息才会走if分支进行消息处理*/
                State msgProcessedState = null;
                if (mIsConstructionCompleted) {
                    /** Normal path */
                    msgProcessedState = processMsg(msg);
                } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
                        && (mMsg.obj == mSmHandlerObj)) {
                    /** Initial one time path. */
                    //设置标志位,表示初始化完成
                    mIsConstructionCompleted = true;
                   //mStateStack从栈底到栈顶,依次调用相应State的enter方法,先父亲后儿子无可厚非
                   //eg. P1.enter -> S1.enter
                    invokeEnterMethods(0);
                } else {
                    throw new RuntimeException("StateMachine.handleMessage: "
                            + "The start method not called, received msg: " + msg);
                }
                //处理状态转换的关键方法
                performTransitions(msgProcessedState, msg);

                // We need to check if mSm == null here as we could be quitting.
                if (mDbg && mSm != null) mSm.log("handleMessage: X");
   //如果收到的消息既不是初始化也不是退出,那么就会调用StateMachine的onPostHandleMessage方法
   //表示消息处理完毕,可以转换到下一个状态
                if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
                    mSm.onPostHandleMessage(msg);
                }
            }
        }
       /**
         * 从指定起始索引stateStackEnteringIndex到栈顶,依次调用相应State的enter方法
         */
        private final void invokeEnterMethods(int stateStackEnteringIndex) {
            for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
                if (stateStackEnteringIndex == mStateStackTopIndex) {
                    // Last enter state for transition
                    mTransitionInProgress = false;
                }
                if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
                mStateStack[i].state.enter();
                mStateStack[i].active = true;
            }
            mTransitionInProgress = false; // ensure flag set to false if no methods called
        }

StateMachine的onPreHandleMessage()和onPostHandleMessage()通过继承覆写这2个方法我们可以进行消息的预处理与结束处理,默认是空实现。
下面进入SmHandler#processMsg方法看看是如何处理后续收到的消息

  /**
         * Process the message. If the current state doesn't handle
         * it, call the states parent and so on. If it is never handled then
         * call the state machines unhandledMessage method.
         * @return the state that processed the message
         */
        private final State processMsg(Message msg) {
            //获取栈顶元素,前文我们提到mStateStack的栈顶元素就是我们设置的初始状态
            StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
            if (mDbg) {
                mSm.log("processMsg: " + curStateInfo.state.getName());
            }
            //如果消息是StateMachine内部发送的退出消息(调用StateMachine#quit方法),
            //则切换到QuittingState
            if (isQuit(msg)) {
                transitionTo(mQuittingState);
            } else {
                while (!curStateInfo.state.processMessage(msg)) {
                    /**
                     * Not processed
                     */
                    curStateInfo = curStateInfo.parentStateInfo;
                    if (curStateInfo == null) {
                        /**
                         * No parents left so it's not handled
                         */
                        mSm.unhandledMessage(msg);
                        break;
                    }
                    if (mDbg) {
                        mSm.log("processMsg: " + curStateInfo.state.getName());
                    }
                }
            }
            return (curStateInfo != null) ? curStateInfo.state : null;
        }

SDK的注释说的很明白,当我们调用StateMachine#start完成初始化后,后续通过StateMachine#sendMessage发送的消息会优先分发给我们设置的初始状态进行处理,如果初始状态不能处理(State#processMessage方法返回false),则交给其父节点处理,依次类推,如果所有状态节点都无法处理,则会交给StateMachine#unhandleMessage进行处理。
看我消息处理函数,下面来看看状态转换处理函数SmHandler#performTransitions

  /**
         * Do any transitions
         * @param msgProcessedState 是上面能处理消息的状态
         */
 private void performTransitions(State msgProcessedState, Message msg) {
       //省略状态机日志相关的代码
            .............
      //mDestState通过StateMachine#translationTo(IState state)赋值
       State destState = mDestState;
          if (destState != null) {
                /**
                 * Process the transitions including transitions in the enter/exit methods
                 */
             while (true) {
                    if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
            /**找到mDestState与当前的初始状态的共同祖先,并设置mTempStateStack
              *如果不存在共同祖先则返回null*/
            StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
                    // flag is cleared in invokeEnterMethods before entering the target state
                    mTransitionInProgress = true;
                    //从当前初始状态到公共状态依次调用State.exit方法(不含公共状态)
                    //如果没有公共状态,则整个mStateStack中的State.exit都会被调用
                    invokeExitMethods(commonStateInfo);
                    //上文已经分析过这2个方法了,作用是使新的状态层级结构进入激活状态
                    int stateStackEnteringIndex = moveTempStateStackToStateStack();
                    invokeEnterMethods(stateStackEnteringIndex);
                /**将延迟消息移动到前台处理队列,也就是说通过StateMachine#deferMessage()方法
                  *发送的消息,只有在进行下一次状态转换时才会被加到前台队列,才有机会被执行
                  */
                    moveDeferredMessageAtFrontOfQueue();
                    //此处涉及并发问题,StateMachine#transitionTo并非线程安全,
                    //所以只要mDestState被改变则继续循环
                    if (destState != mDestState) {
                        // A new mDestState so continue looping
                        destState = mDestState;
                    } else {
                        // No change in mDestState so we're done
                        break;
                    }
                }
                mDestState = null;
            }

            /**
             * After processing all transitions check and
             * see if the last transition was to quit or halt.
             *扫尾工作,可通过StateMachine#transitionToHaltingState()或
             *StateMachine#quit/quitNow进入相应状态
             */
            if (destState != null) {
                if (destState == mQuittingState) {
                    /**
                     * 调用StateMachine的onQuittiing回调方便子类覆写处理自己的垃圾
                     */
                    mSm.onQuitting();
                    cleanupAfterQuitting();
                } else if (destState == mHaltingState) {
                    /**
                     * Call onHalting() if we've transitioned to the halting
                     * state. All subsequent messages will be processed in
                     * in the halting state which invokes haltedProcessMessage(msg);
                     *进入Halting状态,后续消息会通过HaltingState#processMessage
                     *回调交给StateMachine#haltedProcessMessage进行处理
                     */
                    mSm.onHalting();
                }
            }
        }

下面看看是如何找到公共祖先的。

  private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
            /**
             * Search up the parent list of the destination state for an active
             * state. Use a do while() loop as the destState must always be entered
             * even if it is active. This can happen if we are exiting/entering
             * the current state.
             */
            //逻辑上清空mTempStateStack
            mTempStateStackCount = 0;
            StateInfo curStateInfo = mStateInfo.get(destState);
            do {
                mTempStateStack[mTempStateStackCount++] = curStateInfo;
                curStateInfo = curStateInfo.parentStateInfo;
            } while ((curStateInfo != null) && !curStateInfo.active);

            if (mDbg) {
                mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
                        + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
            }
            return curStateInfo;
        }

逻辑很简单,先将destState节点存入mTempStateStack,然后根据destState节点往上回溯,如果该节点为非激活状态则存入mTempStateStack,直到该节点的父节点为激活状态,如果没有公共节点那么往上回溯,返回值肯定是null。
下面看看SmHandler# invokeExitMethods

  /**
         * Call the exit method for each state from the top of stack
         * up to the common ancestor state.
         *从mStateStack的栈顶依次调用State.exit方法直至公共祖先(不含公共祖先)
         */
        private final void invokeExitMethods(StateInfo commonStateInfo) {
            while ((mStateStackTopIndex >= 0)
                    && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
                State curState = mStateStack[mStateStackTopIndex].state;
                if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
                curState.exit();
                mStateStack[mStateStackTopIndex].active = false;
                mStateStackTopIndex -= 1;
            }
        }

该方法就是从初始状态往上回溯调用exit方法,直至公共祖先(不含公共祖先)。
至此StateMachine工作流程相关的代码都已经分析完成。

总结

假设我们经过前三步基本使用步骤构造的状态层次结构图如下所示:

状态层次结构

当我们调用start()方法,进而会调用SmHandler#completeConstruction(),该方法首先会初始化2个状态栈

状态栈的初始化

接着发送一个SM_INIT_CMD消息,当SmHandler#handleMessage(),处理这个初始化消息时,会调用SmHandler#invokeEnterMethods(0),依次从mStateStack的栈底(因为传入参数为0)到栈顶调用对应State.enter()方法,即enter方法的调用顺序为P0->P1->S2->S5,并将State.active设置为true,表示已经激活。

当SmHandler处理通过StateMachine#sendMessage()发送的消息时,会调用SmHandler的processMsg()方法,消息分发逻辑如下:
消息会优先分配给当前的初始状态,如果该状态不能处理该消息(State#processMessage返回false),则分发给其父节点,以此类推,如果所有状态都不能处理,则分发给StateMachine的unhandleMessage方法进行处理,即消息会从mStateStack的栈顶分发至栈底。

处理完消息会调用SmHandler#performTransitions方法,进行状态转移,假设我们调用StateMachine#transitionTo(S4),设置S4为目的状态,performTransitions的主要工作逻辑如下:

  1. 调用SmHandler#setupTempStateStackWithStatesToEnter方法找到目的状态与当前初始状态S5(mStateStack的栈顶元素)的公共祖先即P1,同时对mTempStateStack进行重新赋值:先将目的状态S4入栈,然后根据S4往上回溯,如果节点未被激活则入栈,直到找到一个处于激活状态的节点,该节点即是目的状态与当前初始状态的公共祖先。此时,mTempStateStack逻辑上的结构应当如下图所示。
临时栈
  1. 接着调用SmHandler#invokeExitMethods(commonStateInfo)方法,退出旧的状态,mStateStack依次出栈调用State.exit()方法,直到公共祖先P1(不含公共祖先),即exit的调用顺序为S5->S2,此时mStateStack逻辑上结构如下图所示。
退出后的栈
  1. 将mTempStateStack整合至mStateStack
整合
  1. 调用SmHandler# invokeEnterMethods方法,从公共节点之上依次调用State.enter方法,直到栈顶,即新状态的enter调用顺序为S1->S4。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 1.文章介绍 在Android源码中有很多优秀的代码框架,其中StateMachine可以完成某一个消息在不同的状...
    Young_Allen阅读 6,266评论 9 6
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,003评论 11 349
  • 十三年前曾在QQ空间引用尼采的经典名言激励自己并做诗一首,小诗下留言数条,获封才女、厉害、真牛等赞扬字眼,...
    诗崎阅读 245评论 8 1