Handler看這一篇就夠了

Handler使用

首先來熟悉一下Handler的四種使用方式,如果比較熟悉可以直接跳過:

  1. 通過sendMessage消息機(jī)制來發(fā)送

sendEmptyMessage(int);//發(fā)送一個空的消息
sendMessage(Message);//發(fā)送消息秽澳,消息中可以攜帶參數(shù)
sendMessageAtTime(Message, long);//未來某一時間點(diǎn)發(fā)送消息
sendMessageDelayed(Message, long);//延時Nms發(fā)送消息

接收數(shù)據(jù)

mHandler = new Handler(){
   @Override
   public void handleMessage(Message msg) {
       super.handleMessage(msg);
       switch (msg.what) {
           case 0:
               String data = (String) msg.obj;
               textView.setText("接收數(shù)據(jù):" + data);
               break;
           default:
               break;
       }
   }
};

發(fā)送數(shù)據(jù)

 new Thread(new Runnable() {
     @Override
     public void run() {
         Message msg = new Message();
         msg.what = 0;
         msg.obj = "測試數(shù)據(jù)";
         mHandler.sendMessage(msg);
     }
 }).start();
  1. 使用Post方式留量;

post(Runnable);//提交計(jì)劃任務(wù)馬上執(zhí)行
postAtTime(Runnable, long);//提交計(jì)劃任務(wù)在未來的時間點(diǎn)執(zhí)行
postDelayed(Runnable, long);//提交計(jì)劃任務(wù)延時Nms執(zhí)行

new Thread(new Runnable() {
    @Override
    public void run() {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                tvContent.setText("接收數(shù)據(jù):" + str);
            }
        }, 3000);
    }
}).start();

其實(shí)傳遞Runnable也是最終轉(zhuǎn)換成message通過sendMessage來發(fā)送涨椒,將Runnable賦給message的callback砂沛,
這個callback在Handler取消息的時候會先進(jìn)行判斷,如果不為空就直接調(diào)用了础拨,如果為空才會調(diào)用handMessage方法

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
  1. 使用runOnUiThread方式掠抬;

runOnUiThread其實(shí)是調(diào)用的post

new Thread(new Runnable() {
    @Override
    public void run() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                tvContent.setText("接收數(shù)據(jù):" + str);
            }
        });
    }
}).start();
  1. 通過View的Post方式吼野;

也是調(diào)用的Handler的post方法

new Thread(new Runnable() {
    @Override
    public void run() {
        tvContent.post(new Runnable() {
            @Override
            public void run() {
                tvContent.setText("接收數(shù)據(jù):" + str);
            }
        });
    }
}).start();

Handler組成

Handler的四大成員:

發(fā)送方式 功能介紹
Message 主要功能是進(jìn)行消息的封裝,同時可以指定消息的操作形式
Looper 消息循環(huán)泵两波,用來為一個線程跑一個消息循環(huán)瞳步,每一個線程最多只可以擁有一個
MessageQueue 就是一個消息隊(duì)列,存放消息的地方每一個線程最多只可以擁有一個
Handler 消息的處理者腰奋,handler 負(fù)責(zé)將需要傳遞的信息封裝成Message单起,發(fā)送給Looper,繼而由Looper將Message放入MessageQueue中劣坊。當(dāng)Looper對象看到MessageQueue中含有Message嘀倒,就將其廣播出去。該handler 對象收到該消息后局冰,調(diào)用相應(yīng)的handler 對象的handleMessage()方法對其進(jìn)行處理

需要注意的點(diǎn)是每個線程只有一個Looper和一個MessageQueue测蘑,一個線程中可以有多個Handler,每個Handler會處理各自的消息康二,互相之間沒有影響碳胳,也就是多個Handler的發(fā)送和接收之間值多對多的關(guān)系。

Handler原理分析

首先來看下主線程和子線程創(chuàng)建Handler的不同

  1. 主線程
mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};
  1. 子線程
new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();
    }
}).start();

發(fā)現(xiàn)主線程直接建立即可赠摇,而子線程需要使用Looper.prepare()和Looper.loop方法固逗,這個為啥浅蚪?帶著疑問藕帜,接著往下看烫罩。

從Looper.prepare()開始
當(dāng)Looper.prepare()被調(diào)用時,發(fā)生了什么洽故?

public static void prepare() {
        prepare(true);  //最終其實(shí)執(zhí)行的是私有方法prepare(boolean quitAllowed)中的邏輯
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {   //先嘗試獲取是否已經(jīng)存在一個Looper在當(dāng)前線程中贝攒,如果有就拋個異常。
        //這就是為什么我們不能在一個Thread中調(diào)用兩次Looper.prepare()的原因时甚。
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));  //首次調(diào)用的話隘弊,就創(chuàng)建一個新的Looper。
    }
    
    //Looper的私有構(gòu)造函數(shù)
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);   //創(chuàng)建新的MessageQueue荒适,稍后在來扒它梨熙。
        mThread = Thread.currentThread();         //把當(dāng)前的線程賦值給mThread。
    }
 }

可以看到首次調(diào)用prepare的時候會創(chuàng)建一個Looper實(shí)例放進(jìn)去刀诬,創(chuàng)建Looper實(shí)例的時候會創(chuàng)建一個與之關(guān)聯(lián)的MessageQueue咽扇,那么這個sThreadLoacal是什么呢? sThreadLocal是個靜態(tài)的ThreadLocal<Looper> 實(shí)例(在Android中ThreadLocal固定為Looper)。那么陕壹,Looper.prepare()既然是個靜態(tài)方法质欲,Looper是如何確定現(xiàn)在應(yīng)該和哪一個線程建立綁定關(guān)系的呢?
來看看ThreadLocal的get()糠馆、set()方法嘶伟。

public void set(T value) {
        Thread t = Thread.currentThread();  //同樣先獲取到當(dāng)前的線程
        ThreadLocalMap map = getMap(t);     //獲取線程的ThreadLocalMap
        if (map != null)
            map.set(this, value);           //儲存鍵值對
        else
            createMap(t, value);
}    

public T get() {
        Thread t = Thread.currentThread();   //重點(diǎn)啊又碌!獲取到了當(dāng)前運(yùn)行的線程九昧。
        ThreadLocalMap map = getMap(t);      //取出當(dāng)前線程的ThreadLocalMap。這個東西是個重點(diǎn)毕匀,前面已經(jīng)提到過耽装。忘了的同學(xué)在前面再看看。
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);  
            //可以看出期揪,每條線程的ThreadLocalMap中都有一個<ThreadLocal,Looper>鍵值對掉奄。綁定關(guān)系就是通過這個鍵值對建立的。
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
}

重點(diǎn)來了:每個線程有一個與之關(guān)聯(lián)的Looper凤薛,這個Looper是用ThreadLoacaMap來存儲的姓建,ThreadLoacaMap存儲的就是ThreadLocal對象,而Android中的ThreadLocal就是Looper缤苫,所以存的就是Looper速兔,并且ThreadLocalMap是在每線程中都私有的,這就保證的并發(fā)時的線程安全活玲,這是典型的以空間來換時間涣狗,而我們熟悉的synchronized是以時間換空間谍婉。

創(chuàng)建Handler
平時我們都使用new Handler()來在一個線程中創(chuàng)建Handler實(shí)例,但是它是如何知道自己應(yīng)該處理那個線程的任務(wù)呢

public Handler() {
        this(null, false); 
}
    
public Handler(Callback callback, boolean async) {      //可以看到镀钓,最終調(diào)用了這個方法穗熬。
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();                    //重點(diǎn)啊丁溅!在這里Handler和當(dāng)前Thread的Looper綁定了唤蔗。Looper.myLooper()就是從ThreadLocale中取出當(dāng)前線程的Looper。
        if (mLooper == null) {
            //如果子線程中new Handler()之前沒有調(diào)用Looper.prepare()窟赏,那么當(dāng)前線程的Looper就還沒創(chuàng)建妓柜。就會拋出這個異常。
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;  //賦值Looper的MessageQueue給Handler涯穷。
        mCallback = callback;
        mAsynchronous = async;
}

上面代碼中有mLooper = Looper.myLooper()棍掐,這就是Handler和Looper進(jìn)行了綁定,然后Handler就可以給Looper發(fā)消息了拷况,我們調(diào)用Handler的sendMessage方法也就是往Looper的MessageQueue里邊發(fā)消息

Looper.loop()
我們都知道作煌,在Handler創(chuàng)建之后,還需要調(diào)用一下Looper.loop()蝠嘉,這樣才能循環(huán)起來最疆,那Looper是怎樣把消息準(zhǔn)確的送到Handler中處理

public static void loop() {
        final Looper me = myLooper();   //這個方法前面已經(jīng)提到過了,就是獲取到當(dāng)前線程中的Looper對象蚤告。
        if (me == null) { 
            //沒有Looper.prepare()是要報(bào)錯的努酸!
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;       //獲取到Looper的MessageQueue成員變量,這是在Looper創(chuàng)建的時候new的杜恰。

        //這是個Native方法获诈,作用就是檢測一下當(dāng)前線程是否屬于當(dāng)前進(jìn)程。并且會持續(xù)跟蹤其真實(shí)的身份心褐。
        //在IPC機(jī)制中舔涎,這個方法用來清除IPCThreadState的pid和uid信息。并且返回一個身份逗爹,便于使用restoreCallingIdentity()來恢復(fù)亡嫌。
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {  //重點(diǎn)(敲黑板)!這里是個死循環(huán)掘而,一直等待抽取消息挟冠、發(fā)送消息。
            Message msg = queue.next(); //  從MessageQueue中抽取一條消息袍睡。至于怎么取的知染,我們稍后再看。
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            final long traceTag = me.mTraceTag;   //取得MessageQueue的跟蹤標(biāo)記
            if (traceTag != 0) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));  //開始跟蹤本線程的MessageQueue中的當(dāng)前消息斑胜,是Native的方法控淡。
            }
            try {
                msg.target.dispatchMessage(msg);   //嘗試分派消息到和Message綁定的Handler中
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);      //這個和Trace.traceBegin()配套使用嫌吠。
                }
            }

            final long newIdent = Binder.clearCallingIdentity();   //what?又調(diào)用這個Native方法了掺炭。這里主要是為了再次驗(yàn)證辫诅,線程所在的進(jìn)程是否發(fā)生改變。
            msg.recycleUnchecked();   //回收釋放消息竹伸。
        }
    }

從上面的分析可以知道泥栖,當(dāng)調(diào)用了Looper.loop()之后簇宽,線程就就會被一個for(;;)死循環(huán)阻塞勋篓,每次等待MessageQueue的next()方法取出一條Message才開始往下繼續(xù)執(zhí)行。然后通過Message獲取到相應(yīng)的Handler (就是target成員變量)魏割,Handler再通過dispatchMessage()方法譬嚣,把Message派發(fā)到handleMessage()中處理。

MessageQueue
MessageQueue是一個用單鏈的數(shù)據(jù)結(jié)構(gòu)來維護(hù)消息列表钞它,現(xiàn)在又產(chǎn)生一個疑問拜银,MessageQueue的next()方法是如何阻塞住線程的呢?

Message next() {
        //檢查loop是否已經(jīng)為退出狀態(tài)遭垛。mPrt是Native層的MessageQueue的地址尼桶。通過這個地址可以和Native層的MessageQueue互動。
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;      //時間標(biāo)記锯仪,當(dāng)且僅當(dāng)?shù)谝淮潍@取消息時才為0泵督。因?yàn)樗谒姥h(huán)外面啊庶喜!

        for (;;) {
           //這是一個Native的方法小腊。
           nativePollOnce(ptr, nextPollTimeoutMillis);
           synchronized (this) {       //鎖住MessageQueue
                //獲取當(dāng)前的系統(tǒng)時間,用于后面和msg.when進(jìn)行比較久窟。
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;        //獲得當(dāng)前MessageQueue中的第一條消息
                if (msg != null && msg.target == null) {
                
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {  //這個判斷的意義在于只有到了Message應(yīng)該被發(fā)送的時刻才去發(fā)送秩冈,否則繼續(xù)循環(huán)。
                        //計(jì)算下一條消息的時間斥扛。注意最大就是Integer.MAX_VALUE入问。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {  //應(yīng)該發(fā)送一條消息了。
                        // 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();   //轉(zhuǎn)換消息標(biāo)記為使用過的
                        return msg;         //返回一條消息給Looper稀颁。
                    }
                } else {
                    // 如果取到的Message為null芬失,將時間標(biāo)記設(shè)置為-1。
                    nextPollTimeoutMillis = -1;
                }
        }
    }

可以看到峻村。MessageQueue在取消息(調(diào)用next())時麸折,會進(jìn)入一個死循環(huán),直到取出一條Message返回粘昨。這就是為什么Looper.loop()會在queue.next()處等待的原因
上面方法中出現(xiàn)了一個nativePollOnce(ptr, nextPollTimeoutMillis);函數(shù)的調(diào)用垢啼。線程會被阻塞在這個地方窜锯,實(shí)際上是阻塞在了底層的Looper的epoll_wait()這個地方等待喚醒呢。nativeWake()芭析,通過這個來進(jìn)行喚醒喚醒锚扎,這個方法是在enqueueMessage中調(diào)用的
下面看下enqueueMessage的代碼:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {   //沒Handler調(diào)用是會拋異常的啊
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {       //鎖住MessageQueue再往里添加消息。
            msg.markInUse();        //切換Message的使用狀態(tài)為未使用馁启。
            msg.when = when;        //我們設(shè)置的延遲發(fā)送的時間驾孔。
            //經(jīng)過下面的邏輯,Message將會被“儲存”在MessageQueue中惯疙。實(shí)際上翠勉,Message在MessageQueue中的儲存方式,
            //是使用Message.next逐個向后指向的單鏈表結(jié)構(gòu)來儲存的霉颠。比如:A.next = B, B.next = C...
            Message p = mMessages;  //嘗試獲取當(dāng)前Message
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 如果為null对碌,說明是第一條。
                msg.next = p;   
                mMessages = msg;    //設(shè)置當(dāng)前的Message為傳入的Message蒿偎,也就是作為第一條朽们。
                needWake = mBlocked;
            } else {
            
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //不滿足作為第一條Message的條件時,通過下面的逐步變換诉位,將它放在最后面骑脱。這樣便把Message“儲存”到MessageQueue中了。
                for (;;) {
                    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) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

為什么主線程創(chuàng)建Handler不用Looper
因?yàn)橹骶€程在ActivityThread苍糠,也就是主線程初始化的時候通過Looper.prepareMainLooper()建立好了對應(yīng)的Looper
注意ActivityThread并沒有繼承Thread叁丧,它的Handler是繼承Handler的私有內(nèi)部類H.class。在H.class的handleMessage()中椿息,它接收并執(zhí)行主線程中的各種生命周期狀態(tài)消息歹袁。UI的16ms的繪制也是通過Handler來實(shí)現(xiàn)的。也就是說寝优,主線程中的所有操作都是在Looper.prepareMainLooper()和Looper.loop()之間進(jìn)行的条舔。

ThreadLocal說明

這個在上面已經(jīng)說過了,因?yàn)槭欠浅5闹匾Ψ栽谶@里再強(qiáng)調(diào)一遍孟抗。Android中ThreadLocal的表現(xiàn)形式就是Looper,我們在Looper.prepare()中會創(chuàng)建一個Looper實(shí)例钻心,并將其存入ThreadLocalMap中凄硼,而ThreadLoacalMap是屬于特定線程的,這樣不僅保證的線程安全捷沸,同時也將Looper和對應(yīng)的線程關(guān)聯(lián)起來了摊沉,Looper創(chuàng)建的時候也會創(chuàng)建一個對應(yīng)的MessageQueue

HandlerThread

先來看一個HandlerThread使用的例子:

HandlerThread thread = new HandlerThread("MyHandlerThread");
thread.start();
mHandler = new Handler(thread.getLooper());
mHandler.post(new Runnable(){...});

再看下HandlerThread的源碼

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
}

它的代碼比較短,我們主要來看一下它的run()方法痒给,我們發(fā)現(xiàn)它和普通Thread不同之處在于它在run()方法內(nèi)創(chuàng)建了一個消息隊(duì)列说墨,然后來通過Handler的消息的方式來通知HandlerThread執(zhí)行下一個具體的任務(wù)骏全。由于HandlerThread的run()方法內(nèi)Looper是個無限循環(huán),所以當(dāng)我們不需要使用HandlerThread的時候可以通過qiut()的方法來終止尼斧。

public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
}

quit()實(shí)際上就是讓run()內(nèi)的Looper停止循環(huán)

removemessages

這個是用來移除消息隊(duì)列中對應(yīng)what類型的數(shù)據(jù)姜贡,當(dāng)有postMessageDelay消息進(jìn)入倒計(jì)時的時候可以使其停止,但是如果設(shè)置的時間達(dá)到了棺棵,那么也是沒有作用的
先來看下源碼楼咳,下面是Handler的調(diào)用

// Handler.java
public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}

他會接著調(diào)用MessageQueue里邊的removeMessages

// MessageQueue.java
void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
         Message p = mMessages;

        // 原代碼注釋,Remove all messages at front.
        // 根據(jù)上面?zhèn)魅氲膮?shù)烛恤,p!=null成立母怜,p.target == h成立,object == null 成立
        // 即此處可等同為p.what == what
        // 此處代碼即找到第一個p.what == what的消息并將其移除
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        // 此處即為移除找到第一個后的Message
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                // 同上棒动,此處也可以等同為p.what == what
                if (n.target == h && n.what == what
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

由源碼中可以看出糙申,MessageQueue將Message.what與函數(shù)傳入的what相同的Message從隊(duì)列中移除

一個線程多個Handler

一個線程可以建立多個Handler宾添,但是一個線程只有一個Looper船惨,也只有一個MessageQueue,多個Handler發(fā)送的消息會都放到一個MessageQueue中進(jìn)行遍歷缕陕。重點(diǎn):同一個Handler只能處理自己發(fā)送給Looper的那些Message粱锐,多個Handler之間是互不干擾的,也就是多對多的關(guān)系扛邑。移除相同類型消息的時候怜浅,其他Handler也不會受到影響

尊重作者,尊重原創(chuàng)蔬崩,參考文章:
Handler實(shí)例:https://www.cnblogs.com/whoislcj/p/5590615.html
Handler原理:http://www.reibang.com/p/8862bd2b6a29
HandlerThread:http://www.reibang.com/p/5b6c71a7e8d7
多對多關(guān)系:https://blog.csdn.net/u011573355/article/details/50735604

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恶座,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沥阳,更是在濱河造成了極大的恐慌跨琳,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桐罕,死亡現(xiàn)場離奇詭異脉让,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)功炮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門溅潜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人薪伏,你說我怎么就攤上這事滚澜。” “怎么了嫁怀?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵设捐,是天一觀的道長潦牛。 經(jīng)常有香客問我,道長挡育,這世上最難降的妖魔是什么巴碗? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮即寒,結(jié)果婚禮上橡淆,老公的妹妹穿的比我還像新娘。我一直安慰自己母赵,他們只是感情好逸爵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凹嘲,像睡著了一般师倔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上周蹭,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天趋艘,我揣著相機(jī)與錄音,去河邊找鬼凶朗。 笑死瓷胧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的棚愤。 我是一名探鬼主播搓萧,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宛畦!你這毒婦竟也來了瘸洛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤次和,失蹤者是張志新(化名)和其女友劉穎反肋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斯够,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡囚玫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了读规。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抓督。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖束亏,靈堂內(nèi)的尸體忽然破棺而出铃在,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布定铜,位于F島的核電站阳液,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏揣炕。R本人自食惡果不足惜帘皿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畸陡。 院中可真熱鬧鹰溜,春花似錦、人聲如沸丁恭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牲览。三九已至墓陈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間第献,已是汗流浹背贡必。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痊硕,地道東北人赊级。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像岔绸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子橡伞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容