Android Handler源碼解析-消息機制

前言

網上Handler機制的文章很多,大部分只涉及Jave層部分佑菩,本系列文章會從Java層源碼逐步分析引入到Native層源碼盾沫,讓你徹底了解Handler機制。
關于Handler的使用

一. Handler機制介紹

Handler機制作為一個老生常談的東西我也不多做介紹殿漠,其不外呼就四個類赴精。
Handler:發(fā)送、接收消息
Message:消息載體绞幌,內部保存了發(fā)送消息的Handler和指向下一條Message的引用
MessageQueue:消息隊列蕾哟,實際是一個基于時間的優(yōu)先級隊列,內部保存了一個Message鏈表的頭結點莲蜘,通過對這個頭結點的遍歷實現了Message鏈表的插入谭确、刪除等操作。
Looper:負責創(chuàng)建MessageQueue票渠、開啟消息循環(huán)逐哈、從MessageQueue中取出消息

二. 創(chuàng)建Message

發(fā)送Message首先要創(chuàng)建Message,有人說創(chuàng)建Message還不簡單问顷,直接new一個不就行了嗎昂秃,正常情況下這樣是可行的薯鼠。但是某些情況下,例如開啟了一個線程械蹋,里面在做循環(huán)計算出皇,每次計算完畢后都要通知Handler去更新UI,那么如果每次發(fā)送消息都去new一個Message哗戈,這樣會造成很大的內存浪費郊艘。
Android內部為我們實現了Message的緩存機制,通過Message.obtain()或者Handler.obtain()可從消息緩存池獲取到Message唯咬。
由于Handler.obtain()內部也是調用的Message.obtain()纱注,所以只要分析Message.obtain()即可。

廢話不多說胆胰,上源碼狞贱。

public final class Message implements Parcelable {
    //#1
    //延遲執(zhí)行的時間
    long when;
    //發(fā)送消息的那個Handler
    Handler target;
    //Runnable回調,保存的就是Handler.post()里的那個Runnable
    Runnable callback;
    //指向下一個message
    Message next;

    //靜態(tài)的對象鎖(關于這里為什么要使用靜態(tài)的對象鎖而不使用Message.class可以自己研究下)
    public static final Object sPoolSync = new Object();
    //消息緩存池的頭結點
    private static Message sPool;
    //當前緩存池里Message的個數
    private static int sPoolSize = 0;
    //緩存池最大容量
    private static final int MAX_POOL_SIZE = 50;

    //取出緩存消息
    public static Message obtain() {
        synchronized (sPoolSync) {
            //#2
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;//將下一個節(jié)點設為新的頭結點
                m.next = null;//斷開下一個節(jié)點的鏈接
                m.flags = 0;
                sPoolSize--;//緩存池計數減一
                return m;
            }
        }
        return new Message();
    }

    //暴露給用戶回收消息
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    //回收消息
    void recycleUnchecked() {
        //一系列的重置操作
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            //#3
            if (sPoolSize < MAX_POOL_SIZE) {//緩存池未滿蜀涨,將消息加入鏈表
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
}

以上是Message的部分源碼瞎嬉,有些地方加了注釋,看著很長厚柳,其實只要關注#1氧枣、#2和#3處代碼即可。
#1部分别垮,是Message保存的變量便监,發(fā)送Message時需要攜帶這些參數,具體作用已經添加了注釋碳想。
#2#3是Message緩存池機制的代碼烧董,可以看到,這部分源碼都是靜態(tài)的胧奔,所謂的緩存池就是一個靜態(tài)單向鏈表逊移,通過儲存靜態(tài)變量sPool作為緩存池鏈表的頭結點,sPoolSize表示當前緩存池鏈表的長度葡盗,存取緩存消息只需要通過sPool頭結點操作鏈表即可螟左,實際上緩存池就是一個棧的數據結構啡浊,遵循先進后出的原則觅够。

obtain()

obtain方法用來從緩存池取消息。方法很簡單巷嚣,若緩存池有消息喘先,就將緩存池鏈表的頭結點sPool返回,并將sPool的下一個節(jié)點設為新頭結點廷粒,然后進行sPoolSize--

recycleUnchecked()

系統通過recycleUnchecked方法來回收Message窘拯,當調用此方法红且,會重置當前Message的所有狀態(tài)和變量,并將當前Message設為緩存池鏈表的頭結點涤姊。

Q1:什么時候回收Message

我們知道了發(fā)送消息的時候需要使用obtain()方法進行Message復用暇番,知道了recycleUnchecked()方法能回收當前Message,那么Message在什么時候回收呢思喊,回收需要我們去調用Message.recycle()方法嗎壁酬?

三. 創(chuàng)建Handler

Message已經創(chuàng)建好了,要發(fā)送這個Message還得通過Handler恨课,創(chuàng)建Handler先就要創(chuàng)建Looper舆乔。Android主線程里已經默認創(chuàng)建好了Looper,所以在主線程里使用Handler是不需要我們再去創(chuàng)建Looper的剂公。
主線程創(chuàng)建Handler的過程

public final class ActivityThread extends ClientTransactionHandler {
    public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();//創(chuàng)建主線程Looper
        ......
        ActivityThread thread = new ActivityThread();//這里面會創(chuàng)建主線程Handler
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();//獲取主線程Handler
        }
        ......
        Looper.loop();//開啟消息循環(huán)
        //后面的代碼不會執(zhí)行到希俩,除非當前Lopper的循環(huán)退出了,在主線程就代表當前程序結束運行
    }
}
//#Looper
    public static void prepareMainLooper() {
        prepare(false);//也是調用的prepare()方法
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

可以看到主線程使用Handler也是先通過prepare()創(chuàng)建Looper纲辽,之后創(chuàng)建Handler颜武,最后調用Looper.loop()開啟消息循環(huán)。

Q2:Handler創(chuàng)建流程為什么一定要Looper.prepare()->創(chuàng)建Handler->Looper.loop()
1. 先看第一步拖吼,創(chuàng)建Looper
//#Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    //通過prepare創(chuàng)建Looper
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

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

Looper的創(chuàng)建過程非常簡單盒刚,先調用Looper的構造函數,Looper的構造函數里創(chuàng)建了一個MessageQueue绿贞,隨后將創(chuàng)建的Looper設置為ThreadLocal變量因块,通過ThreadLocal可以確保一個線程對應一個Looper(若對ThreadLocal的用法不太了解可以去網上尋找資料)

2. 創(chuàng)建Handler
public class Handler {
    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;
    final boolean mAsynchronous;

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();//獲取當前線程Looper
        if (mLooper == null) {//如果當前線程沒有創(chuàng)建過Looper就拋出異常
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//獲取當前線程的消息隊列
        mCallback = callback;//Handler Callback寫法的回調
        mAsynchronous = async;//是否異步消息
    }
}
//#Looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

Handler構造函數主要做了一件事,先去拿當前線程的Looper籍铁,通過Looper再拿到MessageQueue涡上。

Q2的第一點:創(chuàng)建Handler之前要調用Looper.prepare()
看源碼可知Handler需要拿到MessageQueue,而MessageQueue是在Looper里創(chuàng)建的拒名,所以創(chuàng)建Handler前必須先創(chuàng)建Looper才能拿到MessageQueue吩愧。

3. Looper.loop()開啟消息循環(huán)
//#Looper
    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;//由于MessageQueue是Looper里的對象,所以MessageQueue里取出的Message都是在Looper線程里增显,即不管在哪個線程發(fā)送消息雁佳,接收消息都是在調用了Looper.prepare()方法的那個線程接收。

        for (;;) {//所謂的消息循環(huán)其實就是一個死循環(huán)同云,作用是不斷的從MessageQueue里面讀取消息
            Message msg = queue.next(); //這是一個阻塞方法糖权,當MessageQueue里沒有下一條消息或者下一條消息是延遲消息時會進入阻塞狀態(tài)
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);//內部會回調Handler的handlerMessage()方法或者Runnable的run()方法
            } catch (Exception exception) {
                throw exception;
            } finally {
            }
            msg.recycleUnchecked();//回收Message
        }
    }

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

以上代碼是精簡過的,loop()方法里開啟了一個無限for循環(huán)炸站,不斷的調用MessageQueue的next()方法嘗試從消息隊列里面讀取Message星澳,而next()方法是一個阻塞方法,當沒有可讀消息時會進入阻塞狀態(tài)且不占用cpu資源旱易,這樣盡管loop中是一個死循環(huán)也不會造成卡死的情況禁偎。
在拿到Message后腿堤,會調用msg.target.dispatchMessage(msg),而target其實就是Handler如暖,來看Handler的dispatchMessage(msg)方法

//#Handler
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }

很簡單笆檀,先判斷msg是否有設置Runnable回調,若有設置就去調用Runnable的run(),若沒有設置Runnable盒至,則根據構造Handler的方式去回調Callback的handleMessage(msg)或者回調Handler的handleMessage(msg)
當通過handler.post()設置了Runnable回調時误债,只回調Runnable的run方法,不會去執(zhí)行handler.handleMessage方法

Looper構造函數要創(chuàng)建MessageQueue妄迁、Handler構造函數要拿到Looper里的MessageQueue寝蹈、Looper.loop()開啟消息循環(huán)也是從MessageQueue里取消息,可見消息的傳遞應該都是圍繞MessageQueue進行的登淘,那么MessageQueue是何方大佬竟然這么重要箫老,接下來就要分析它。

Q1:什么時候回收Message黔州。
通過以上源碼可知耍鬓,在系統調用handler.dispatchMessag()后就會調用msg.recycleUnchecked()方法將Message回收,所以一般情況下流妻,并不需要我們去特意回收Message牲蜀。
當然,在調用messageQueue.removeMessages等方法移除消息隊列中的消息時也會將Message回收绅这。

Q2的第二點:為什么要先創(chuàng)建Handler才能去Looper.loop()涣达。
由于loop()方法里是一個死循環(huán),所以循環(huán)后面的代碼都不會執(zhí)行到证薇,所以必須先創(chuàng)建Handler才能去調用Looper.loop()

四. 發(fā)送Message

發(fā)送消息有發(fā)送Message和發(fā)送Runnable兩種方式度苔,其實發(fā)送Runnable也是發(fā)送Message,只不通過getPostMessage()方法過把Runnable變成Message的一個callback參數

//#Handler
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

無論通過Handler的哪種方式發(fā)送浑度,最后都會調用MessageQueue的enqueueMessage(msg, uptimeMillis)方法寇窑。
借用下其它博客的一張圖片


五. MessageQueue

1. 先看enqueueMessage()方法
//#MessageQueue
    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) {//#1,插入頭結點
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {//#2箩张,根據when插入到合適的位置
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }
            if (needWake) {//#3
                nativeWake(mPtr);//nativa方法甩骏,作用是喚醒nativePollOnce方法的阻塞
            }
        }
        return true;
    }

首先了解下when是什么:when=SystemClock.uptimeMillis() + delayMillis,SystemClock.uptimeMillis()是開機到現在的毫秒數先慷,那么when就是一個時間節(jié)點
#1:將新消息msg的when和頭結點mMessages的when作比較饮笛,若msg是早于mMessages執(zhí)行的,就將mMessages設為msg的next熟掂,然后將msg設為新的mMessages缎浇,這樣msg就插入到了鏈表的頭結點扎拣。
#2:若msg不能插入鏈表頭結點赴肚,則開啟循環(huán)素跺,不斷比較鏈表下一個元素的when和msg的when大小,直到msg的when小于某個節(jié)點的when誉券,就將msg插入此處指厌。
#3:調用nativeWake(mPtr),喚醒nativePollOnce(ptr, nextPollTimeoutMillis)踊跟。
簡單來說踩验,enqueueMessage()方法的作用就是通過比較when的大小,將新msg消息插入消息鏈表對應的位置(即msg的when小于鏈表某節(jié)點的when時對應的那個位置)

2. MessageQueue.next()

知道了消息是怎么存到MessageQueue里的商玫,那么怎么從MessageQueue里取消息呢箕憾?答案就是之前在Looper.loop()里用到的next()方法,我們知道loop()會不斷循調用MessageQueue.next()拳昌,那么MessageQueue.next()應該就是用來取出MessageQueue里鏈表下一條消息的袭异,具體怎么取呢?請看源碼炬藤。

//#MessageQueue
    @UnsupportedAppUsage
    Message next() {
        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();
            }

            //#1
            //nextPollTimeoutMillis<0 一直阻塞
            //nextPollTimeoutMillis=0 不阻塞
            //nextPollTimeoutMillis>0 阻塞nextPollTimeoutMillis時間
            nativePollOnce(ptr, nextPollTimeoutMillis);//nativa的阻塞方法

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //#2
                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) {
                    //#3
                    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;
        }
    }

代碼比較長御铃,挑重點看。
可以看到沈矿,next()方法里也開啟了一個for循環(huán)上真。進入循環(huán)后會調用nativePollOnce(ptr, nextPollTimeoutMillis)方法進入阻塞狀態(tài),該方法是一個nativa的阻塞方法羹膳,喚醒該方法的方式有兩種
第一種方式:nextPollTimeoutMillis指定的延遲時間已到睡互,會自動喚醒該方法。
第二種方式:當有新消息插入后陵像,會調用nativeWake方法喚醒nativePollOnce湃缎,nativeWake方法在之前分析enqueueMessage已經說明。

#2處代碼暫時不分析蠢壹,先看#3處代碼嗓违。
#3:判斷當前時間和msg的時間大小,若當前時間小于msg時間图贸,則說明這條新消息需要延遲返回蹂季,設置nextPollTimeoutMillis 延遲的時間為msg.when - now,之后進入下一次循環(huán)并進入阻塞狀態(tài)疏日,等待延遲時間到或者有新消息加入喚醒阻塞偿洁。
若當前時間大于等于msg時間,說明這條新消息需要立即返回沟优,取出消息鏈表的頭結點返回并將頭結點的next節(jié)點設為新的頭結點涕滋。

這時有人會問loop()里面也是一個for循環(huán),取個消息需要兩個for循環(huán)嗎挠阁?這就要提到我之前說的那句話了宾肺,MessageQueue不是一個隊列溯饵,按照正常的邏輯,若MessageQueue是一個隊列锨用,那么根據先進先出的原則丰刊,next()方法應該立即返回最早push進去的那條消息,實際上并不一定增拥。因為Message中有一個很重要的變量when存在啄巧,假設next()里面沒有循環(huán),使得即使在loop()里調用next()方法掌栅,代碼運行到nativePollOnce方法進入阻塞狀態(tài)秩仆,當有新延遲消息加入喚醒nativePollOnce方法時,由于舊消息延遲時間還未到猾封,而新加入的消息也是延遲消息逗概,這時next()方法便取不到消息只能返回null,而MessageQueue里明顯是有消息的忘衍,顯然這不合理逾苫。
loop的循環(huán)負責從MessageQueue里不斷地取出消息,next()里的循環(huán)負責從消息鏈表正確的取出對應的那條消息

Handler機制大致原理分析完了枚钓,不過肯定還有很多小伙伴云里霧里铅搓,我們先來總結一下。
  1. 一個線程對應一個Looper搀捷,一個Looper對應一個MessageQueue星掰,創(chuàng)建Looper時會順帶創(chuàng)建了MessageQueue
  2. Handler機制消息輪詢的三個重要方法,MessageQueue.enqueueMessage嫩舟、MessageQueue.next氢烘、Looper.loop
    MessageQueue.enqueueMessage:往鏈表插入一條消息
    MessageQueue.next:阻塞方法,從鏈表取出一條消息
    Looper.loop:從MessageQueue里循環(huán)讀取消息
  3. 發(fā)送消息時家厌,Runnable也會被封裝成一個Message播玖,最終會調用MessageQueue的enqueueMessage(msg, uptimeMillis)方法
這里再說兩個常見的問題

Q1:主線程中l(wèi)oop死循環(huán)為什么不會造成程序卡死
A:Android是消息驅動的,每一次事件饭于、更新UI等響應都是在loop里面進行蜀踏,正是由于loop中是一個死循環(huán),這才可以保證程序運行后不會結束掰吕,且在沒有響應事件時果覆,loop會進入阻塞狀態(tài)釋放CPU資源,以上原因保證了程序的正常運行殖熟。
Q2:Handler是怎么切換線程的
A:線程切換的原理就是線程間的資源共享局待。因為Looper是一個ThreadLocal變量且屬于創(chuàng)建Handler時的線程,所以可以保證無論發(fā)送消息在哪個線程,通過Looper從MessageQueue取出的消息都是在Handler線程钳榨。

下一篇講講Handler屏障機制舰罚。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市重绷,隨后出現的幾起案子沸停,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砍濒,死亡現場離奇詭異翠储,居然都是意外死亡,警方通過查閱死者的電腦和手機假颇,發(fā)現死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人倒淫,你說我怎么就攤上這事“苡瘢” “怎么了敌土?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長运翼。 經常有香客問我返干,道長,這世上最難降的妖魔是什么血淌? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任矩欠,我火速辦了婚禮,結果婚禮上悠夯,老公的妹妹穿的比我還像新娘癌淮。我一直安慰自己,他們只是感情好沦补,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布乳蓄。 她就那樣靜靜地躺著,像睡著了一般夕膀。 火紅的嫁衣襯著肌膚如雪栓袖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天店诗,我揣著相機與錄音裹刮,去河邊找鬼。 笑死庞瘸,一個胖子當著我的面吹牛捧弃,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼违霞,長吁一口氣:“原來是場噩夢啊……” “哼嘴办!你這毒婦竟也來了?” 一聲冷哼從身側響起买鸽,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤涧郊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后眼五,有當地人在樹林里發(fā)現了一具尸體妆艘,經...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年看幼,在試婚紗的時候發(fā)現自己被綠了批旺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡诵姜,死狀恐怖汽煮,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情棚唆,我是刑警寧澤暇赤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站宵凌,受9級特大地震影響鞋囊,放射性物質發(fā)生泄漏。R本人自食惡果不足惜摆寄,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一失暴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧微饥,春花似錦逗扒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肃续,卻和暖如春黍檩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背始锚。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工刽酱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瞧捌。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓棵里,卻偏偏與公主長得像润文,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子殿怜,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容