Android系統源碼分析--消息循環機制

上一章我們講解SystemServer時涉及到了消息機制,因此這一章我們先介紹一下消息循環機制,幫助大家弄清楚消息循環的原理,有助于代碼的編寫和優化。

Looper-Message-MessageQueue-Handler消息處理機制

在Android系統有兩個通信機制,一個是Binder,一個是消息機制,前者是跨進程通信,后者是進程內部通信。消息通信主要包括幾個部分:

  • 消息發送者和處理者:Handler
  • 消息循環器:Looper
  • 消息隊列:MessageQueue
  • 消息:Message

我們先看一個時序圖:

005.jpg

圖中,1-11步是Looper的準備過程,12-17步是獲取消息,處理消息,回收消息的循環過程。

下面是一張消息循環過程圖,圖片來自網絡博客(blog.mindorks.com),Looper會通過loop方法不斷從消息隊列去取消息,然后交給handler處理,處理完成就回收消息,要注意的是只有一個looper,但是可能有多個handler:

002.jpg

1、Looper

Looper是一個循環器,通過里面的loop方法不斷去取消息,發送給Handler進行處理。根據上面時序圖以及SystemServer啟動代碼我們開始分析Looper的調用過程:

private void run() {
        try {
            ...
            
            // 準備主線程的Looper
            Looper.prepareMainLooper();

            ...
        } finally {
            ...
        }

        ...

        // Loop(循環) forever.
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

我們先看Looper.prepareMainLooper方法:

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

上面有段注釋,我翻譯一下,就是:初始化當前線程作為一個looper,并把它標記為應用的主looper。這個looper是被Android環境(系統)創建的,因此你不需要自己調用這個方法。也就是系統創建了這個looper,你不需要再創建了。我們接著看里面的內容,首先調用了prepare方法,需要注意的是Looper.prepare()在每個線程只允許執行一次,該方法會創建Looper對象:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
...
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {// 確保ThreadLocal中只有一個Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

上面的ThreadLocal是聲明在類里的,并且是靜態的,因此,隨著類創建了該對象,get方法是獲取Looper的,如果能獲取到,則拋出異常,也就是確保當前線程只有一個Looper。如果是空,那么我們創建一個Looper放到里面去。

我們先看一下ThreadLocal:線程本地存儲區(Thread Local Storage,簡稱為TLS),每個線程都有自己的私有的本地存儲區域,不同線程之間彼此不能訪問對方的TLS區域。(來自Android消息機制1-Handler(Java層))我們看一下它的set和get方法:

ThreadLocal的set方法:

    public void set(T value) {
        //獲取當前線程
        Thread t = Thread.currentThread();
        // 獲取當前線程里的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

如果map不為空,則以鍵值對放入進行存儲,此處map不是HashMap,而是其他,這里不詳細解釋。如果map為空,則通過下面代碼創建map:

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal的get方法:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

我們看到獲取的時候也是根據當前線程去獲取的。因此每個線程會保存一個Looper。

我們接著看Looper的構造函數有哪些操作,也就是創建Looper做了哪些處理:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

首先是創建了MessageQueue對象,接著創建一個線程,也就是當前線程,currentThread是一個native方法,我們不再分析,我們看一下MessageQueue創建做了哪些事情:

    MessageQueue(boolean quitAllowed) {
        // 是否可以退出消息隊列
        mQuitAllowed = quitAllowed;
        // 返回底層的MessageQueue對象的內存地址,如果為空返回0
        mPtr = nativeInit();
    }

上面的nativeInit是調用的jni,我貼一下代碼,不再解釋:

001.png

我們回到prepareMainLooper方法接著看,如果sMainLooper不為null,則拋出異常,提示sMainLooper已經創建了,如果是null,那么調用myLooper方法回去sMainLooper:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

其實這個get方法就是我們上面new完Looper放進去的,到此prepareMainLooper就完成了,相關信息也準備好了。接下來就是調用Looper.loop方法,方法下面是一個異常,怎么樣才能保證異常不會拋出,就是loop方法永遠執行不完。是不是只有我們接著看代碼:

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        ...

        for (;;) {// 無限循環
            Message msg = queue.next(); // might block
            if (msg == null) { // message為空為結束信號,退出循環
                // No message indicates that the message queue is quitting.
                return;
            }

            ...
            
            try {
                // 將真正的處理工作交給message的target,即handler
                msg.target.dispatchMessage(msg);
            } finally {
                ...
            }

            ...

            // 回收Message
            msg.recycleUnchecked();
        }
    }

首先是通過myLooper方法獲取Looper,如果為空,則拋出異常,提示還沒有調用Looper.prepare方法,如果不為空,則通過looper獲取MessageQueue對象,然后進入for循環,因為for語句中沒有條件,因此該for循環為無限循環,在這個循環中有三件事,一個是獲取消息隊列中的下一個消息,然后處理該消息,最近處理完消息,回收消息。這三個過程就是Looper的主要作用:取消息,處理消息,回收消息。

2.Message

Message是整個循環中信息的載體,它是一個鏈表結構,關于鏈表結構可以參考下面文章:
Android自助餐--Handler消息機制完全解析--系列
鏈表數據結構圖解 和 代碼實現
基本數據結構:鏈表(list)
鏈表結構之單鏈表

我們看個圖:

006.png

上面就是一個示例圖,每個Message中都有一個后面Message的引用next,鏈表最后一個next為空,sPool是第一個Message。但是每個Message的內存地址不是挨著的,這樣可以占用零碎的內存。

我們先來看Message包含的參數:

    public int what;
    public int arg1; 
    public int arg2;
    public Object obj;
    /*package*/ int flags;
    /*package*/ long when;
    /*package*/ Handler target;
    // 消息隊列中下一個消息的引用
    /*package*/ Message next;
    // sPool這個變量可以理解為消息隊列的頭部的指針,也就是當前消息對象
    private static Message sPool;
    // sPoolSize是當前的消息隊列的長度
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;    

前四個參數很熟悉,不再解釋,flags是一個標簽,表示是否正在使用;when是處理消息的時間;target就是我們上面提到的Handler;next是下一個Message的引用;sPool是一個靜態變量,說明只有一個,其實這個是消息隊列的頭消息;sPoolSize是消息隊列中消息個數;MAX_POOL_SIZE是消息隊列最大消息數量。

Message中有多個用來獲取Message對象的obtain復寫方法。因為后面的obtain方法都是通過第一個obtain方法獲取Message對象的,因此我們只看第一個參數為空的方法:

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        // 避免多線程進行爭搶資源,給sPoolSync進行加鎖
        synchronized (sPoolSync) {
            // 如果消息隊列的頭部不為空,則可以取出頭部重用
            if (sPool != null) {
                Message m = sPool;
                // 頭部消息取出后,將sPool指向后面的消息對象
                sPool = m.next;
                // next(隊列尾部)設置為null
                m.next = null;
                m.flags = 0; // clear in-use flag
                // 消息隊列長度減一
                sPoolSize--;
                return m;
            }
        }
        // 如果消息隊列的頭部為空,則創建新的Message對象
        return new Message();
    }

系統提示盡量用這種方法獲取Message對象,避免創建大量新的對象,其實也可以通過Handler來獲取Message,這個我們在將Handler時候再講。

在上面Looper中我們講到最后消息處理完后需要回收,這個回收方法recycleUnchecked也在Message類中:

 /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        // 避免多線程進行爭搶資源,給sPoolSync進行加鎖
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                // 回收當前消息后時,將sPool消息后移
                next = sPool;
                // 將當前消息放到頭部
                sPool = this;
                // 隊列長度加一
                sPoolSize++;
            }
        }
    }

消息回收時,將對應消息的標簽設置為使用中,其他標簽設置為空或者默認值,如果消息隊列沒有超過最大值,那么將sPool賦值給next,將這個Message賦值給sPool,消息隊列長度加一。也就是將處理完的消息清空,重新放回消息隊列等待使用。

3.Handler

Handler是發送消息和處理消息的工具。我們先看構造方法:

public Handler(Callback callback, boolean async) {
        ...

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Handler中的looper是獲取當前線程中的looper,looper不能為空,MessageQueue也是looper中的。

首先是發送消息,發送消息的方法很多,我們看一張圖:

004.jpg

我們看到Handler中有多個發送消息的方法,但是最終調用了enqueueMessage方法:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

從代碼我們可以看到msg.target就是Handler,也就是在這里進行賦值的,然后是調用MQ(MessageQueue)的enqueueMessage方法,這個方法是添加消息隊列的,具體內容我們后面再講。因此,發送消息就是講消息添加到消息隊列。我們前面還講過獲取Message對象可以通過Message中的obtain方法,也可以通過Handler中的方法,我們先看一張圖:

003.jpg

Handler是通過多個復寫方法obtainMessage來獲取Message的,只是傳入參數不同,我們看一個沒有參數的方法代碼:

    public final Message obtainMessage(){
        return Message.obtain(this);
    }

我們看到其實還是調用了Message.obtain方法,并且傳入了this,也就是Handler,通過Message.obtain方法將Handler賦值給Message中的target。從這,我們基本對Handler與Message的關系基本明確了,獲取Message的方法我們也完全知道了,因此我們在以后用的時候不需要再去new一個Message對象,而是通過obtain方法去獲取,如果有就不需要new了,如果沒有系統會自己去創建。

4.MessageQueue

MessageQueue是消息隊列,其實是管理消息鏈表的。它主要功能是取出消息--next方法,將消息加入隊列--enqueueMessage方法。

我們先看加入消息隊列方法enqueueMessage,也就是Handler中發送消息后加入隊列的方法:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (; ; ) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

插入消息隊列有兩種情況,一種是消息隊列處于空閑狀態,直接將消息放在消息隊列前面,可能需要喚醒主線程,另一種是消息隊列處于忙碌狀態,就不需要喚醒,而是根據消息處理時間將消息插入到消息隊列的對應位置中。

第一種狀態:插入隊列頭

if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
}

if語句的三個條件是:一、隊列為空,二、插入消息需要立即處理,三、插入消息處理時間比消息隊列頭消息早,這三個條件說明消息隊列處于閑置狀態,此時要把消息放置到消息隊列頭部,即將插入消息的next指向消息隊列的頭p,然后將消息隊列要處理的消息指向插入消息對象,最后判斷是否需要喚醒,如果隊列阻塞則需要喚醒,否則不需要。

第二種狀態:插入隊列中間或者后面,這種情況比較復雜

needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (; ; ) {
    prev = p;
    p = p.next;
    if (p == null || when < p.when) {
        break;
    }
    if (needWake && p.isAsynchronous()) {
        needWake = false;
    }
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;

因為不是在隊列頭,所以需要for循環去查找應該的位置,首先將第一個消息用prev進行緩存,然后當前消息引用指向下一個消息對象,依次類推,直到p == null(到隊列最后),或者當前消息觸發時間小于后面這個消息的觸發時間,停止循環,說明找到了位置,此時執行最后兩行代碼,也就是將當前出入消息的next指向p,也就是,如果p==null,則說明插入到最后一個,如果不為空,則插入到p前面,然后將前一個prev的next指向插入的消息,此時插入成功。最后的if語句中如果需要喚醒消息隊列,則調用底層方法nativeWake喚醒消息隊列開始循環。到此,消息插入就講完了。我們上面說到loop方法是通過MessageQueue的next方法取出消息,那么下面我們看一下next方法是怎么取出消息的。

    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (; ; ) {// 死循環
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {// 未到執行時間
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

mPtr是MessageQueue初始化的時候通過調用底層方法獲取的底層NativeMessageQueue的對象,如果底層不能初始化則返回0,如果可以初始化返回對象地址,此處判斷,如果沒有初始化也就沒有底層的NativeMessageQueue對象,因此返回null。緊接著開始for循環,開始遍歷消息隊列,查找需要處理的消息,在這里,如果消息隊列為空,或者沒有需要立即處理的消息都要使線程開始等待。接著調用nativePollOnce方法來查看當前隊列中有沒有消息,傳入參數nextPollTimeoutMillis表示要等待的時間,如果nextPollTimeoutMillis為0則說明不需要等待。接著獲取當前時間now,初始化prevMsg來緩存消息,初始化msg來緩存當前消息mMessages,下面if語句判斷消息不為空但target為空,則說明該消息為“同步分隔欄”(關于“同步分隔欄”請參看聊一聊Android的消息機制一文),如果該消息為同步分隔欄,則后面的同步消息都不會被查找,只能查找異步消息來處理,也就是do-while語句中的代碼,如果沒有同步分割欄或者找到了后面的異步消息(可能沒有),則接著判斷。

如果消息不為空,則還有消息,開始判斷時間,如果當前時間小于下一個消息的執行時間,說明還需要等待,那么計算需要等待的時間nextPollTimeoutMillis,如果當前時間不小于當前消息執行時間時,并且前一個消息prevMsg不為空,說明出現了“同步分隔欄”,也就是執行了do-while代碼,do-while執行完,說明找到了異步消息或者遍歷完整個隊列沒有異步消息,如果有異步消息,此時prevMsg.next = msg.next,也就是跳過同步消息,將異步消息msg.next賦值給prevMsg.next,然后將取出的msg的next賦值為null,因為要處理了,所以不再指向后面隊列的消息對象,然后將msg設置為正在使用,并且返回,如果prevMsg為空,則說明沒有出現“同步分隔欄”,此時將當前消息mMessages的下一個消息賦值給mMessages,然后將msg.next設置為空,就是不再引用,然后設置為正在使用,返回該消息。

如果消息為空,則nextPollTimeoutMillis = -1,說明沒有消息了,則接著向下執行,如果退出消息隊列,則說明所有消息都執行完了,最終調用nativeDestroy方法,如果不退出消息隊列,則要進入等待狀態。如果第一次進入,并且當前消息為空或者消息不為空,但是處于等待狀態,那么要獲取IdleHandler個數,如果小于等于0,則說明沒有IdleHandler運行,調用continue執行下一次循環,如果IdleHandler個數大于0,但是等待的Handler(mPendingIdleHandlers)為空,則要創建IdleHandler數組,將mIdleHandlers放入數據,然后for循環調用每個IdleHandler的queueIdle方法,如果這個方法返回false,則從數組移除這個對象,否則保留改對象,下次空閑繼續執行,最后將pendingIdleHandlerCount置為0,nextPollTimeoutMillis置為0,繼續下一次循環。

那么到此,整個循環就講完了,因為不懂C++代碼,所以底層沒法分析,只能分析framework層代碼,說了很多還是需要自己對比代碼多理解。

5.Handler的使用方法

我們在使用Handler的時候軟件會提示我們有問題,那么到底該怎么寫Handler呢,我從Stack Overflow找到了答案,在這就分享一下:

首先,定義一個靜態MxHandler繼承Handler,里面使用弱引用:

public abstract class MxHandler<T> extends Handler {

    private WeakReference<T> weak;

    public MxHandler(T t) {
        this.weak = new WeakReference<T>(t);
    }

    @Override
    public void handleMessage(Message msg) {
        if (null == weak || null == weak.get()) {
            return;
        }
        handleMessage(msg, weak);
        super.handleMessage(msg);
    }

    protected abstract void handleMessage(Message msg, WeakReference<T> weak);
}

然后我們再寫具體的MyHandler繼承這個MxHandler:

private static final class MyHandler extends MxHandler<HandlerDemo> {

        public MyHandler(HandlerDemo handlerDemo) {
            super(handlerDemo);
        }

        @Override
        protected void handleMessage(Message msg, WeakReference<HandlerDemo> weak) {
            switch (msg.what) {
                case 0:
                    HandlerDemo h = weak.get();
                    h.doSomething();
                    break;
                default:
                    break;
            }
        }
    }

這樣我們在Activity中使用是不會出現內存泄漏之類的錯誤。

參考:

android的消息處理機制(圖+源碼分析)——Looper,Handler,Message
Android中Thread、Handler、Looper、MessageQueue的原理分析
Android 異步消息處理機制 讓你深入理解 Looper、Handler、Message三者關系
Android應用程序消息處理機制(Looper、Handler)分析

原文地址:Android系統源碼分析--消息循環機制

Android開發群:192508518

微信公眾賬號:Code-MX


注:本文原創,轉載請注明出處,多謝。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容