Android 并發(fā)之Handler、Looper匈棘、MessageQueue和ThreadLocal消息機(jī)制原理分析

Android 線程簡單分析(一)
Android 并發(fā)之synchronized鎖住的是代碼還是對象(二)
Android 并發(fā)之CountDownLatch、CyclicBarrier的簡單應(yīng)用(三)
Android 并發(fā)HashMap和ConcurrentHashMap的簡單應(yīng)用(四)(待發(fā)布)
Android 并發(fā)之Lock析命、ReadWriteLock和Condition的簡單應(yīng)用(五)
Android 并發(fā)之CAS(原子操作)簡單介紹(六)
Android 并發(fā)Kotlin協(xié)程的重要性(七)(待發(fā)布)
Android 并發(fā)之AsyncTask原理分析(八)
Android 并發(fā)之Handler主卫、Looper、MessageQueue和ThreadLocal消息機(jī)制原理分析(九)
Android 并發(fā)之HandlerThread和IntentService原理分析(十)

Handler消息機(jī)制在Android中的重要性鹃愤,在Android中Handler消息機(jī)制其實(shí)也就涉及幾個(gè)組件:Handler簇搅、Looper、MesageQueue软吐、Mesage和ThreadLocal瘩将,下面來一個(gè)一個(gè)說說?

先拋出幾個(gè)異常凹耙?

  • Handler消息機(jī)制的流程是什么姿现?
  • Handler如何處理延遲消息?
  • 為什么消息入隊(duì)列和消息出隊(duì)列會(huì)加同步代碼塊肖抱?會(huì)出現(xiàn)呢什么問題备典?
  • 消息時(shí)如何進(jìn)入隊(duì)列的?
  • 消息屏障也就是msg.target == null的消息的作用是什么或者如何處理的意述?

Handler消息機(jī)制的流程

Handler機(jī)制.png

Handler創(chuàng)建的時(shí)候會(huì)獲取當(dāng)前線程的Looper提佣,通過Looper獲取MessageQueue吮蛹,然后通過Handler發(fā)消息,最后Looper不斷輪訓(xùn)取出消息交給Handler處理拌屏。

public Handler(Callback callback, boolean async) {
    //獲取當(dāng)前線程的Looper
    mLooper = Looper.myLooper();
    //如果獲取不到Looper則拋出異常匹涮,一般主線程中默認(rèn)系統(tǒng)已經(jīng)創(chuàng)建了Looper
    if (mLooper == null) {
    // 如果是在工作線程中,就需要手動(dòng)調(diào)用Looper.prepare()創(chuàng)建Looper
        throw new RuntimeException( "Can't create handler inside thre" + Thread.currentThread()+ " that has not called Looper.prepare()");
    }
    //獲取到Looper的MessageQueue
    mQueue = mLooper.mQueue;
    mCallback = callback;
    //是否是異步槐壳,后面會(huì)講
    mAsynchronous = async;
}

源碼有注釋就不解析了,在看看sendMessageAtTime方法喜每。

// 不管是同步消息還是異步消息最終發(fā)消息都會(huì)調(diào)用此方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    //獲取消息隊(duì)列
    MessageQueue queue = mQueue;
    if (queue == null) {
        // 如果沒有消息隊(duì)列务唐,則拋異常
        RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
        return false;
    }
    // 消息準(zhǔn)備進(jìn)入隊(duì)列
    return enqueueMessage(queue, msg, uptimeMillis);
}

在sendMessageAtTime方法最終調(diào)用enqueueMessage 方法并調(diào)用MessageQueue的enqueueMessage(msg, uptimeMillis)把消息放入隊(duì)列:

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

來看看消息是如何加入MessageQuueue:

 boolean enqueueMessage(Message msg, long when) {
 //這里和next方法取出消息時(shí)判斷msg.target == null不矛盾,因?yàn)橄到y(tǒng)內(nèi)發(fā)消息并不經(jīng)過此方法
    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带兜?
    //因?yàn)橄㈥?duì)列內(nèi)部維護(hù)的是單鏈表枫笛,線程不安全,消息入隊(duì)列可能同時(shí)有消息出隊(duì)列
    // 那么消息出隊(duì)列可能會(huì)把消息移除刚照,還有一種情況就是入隊(duì)時(shí)可能會(huì)造成順序錯(cuò)亂
    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;
        }

        //標(biāo)志消息正在使用
        msg.markInUse();
        msg.when = when;
        // 下面就是鏈表的操作了
        Message p = mMessages; //head
        boolean needWake; // 需不需要阻塞
          //如果消息為null刑巧、時(shí)間為0或者時(shí)間比隊(duì)頭的小,直接插入在對頭无畔,如果隊(duì)列正在阻塞就喚醒
        if (p == null || when == 0 || when < p.when) {  //消息入隊(duì)列啊楚,按照時(shí)間從小到大排序
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 只有在Delay的消息 mBlocked = true并且是異步的,異步消息優(yōu)先級高的很
          //插入隊(duì)列的中間浑彰。 通常我們不必喚醒事件隊(duì)列恭理,除非隊(duì)列頭部有屏障,并且消息是隊(duì)列中最早的異步消息郭变。
            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;
            prev.next = msg;
        }
        /**
         * 在next中颜价,如果有阻塞(沒有消息了或者只有Delay的消息),會(huì)把mBlocked這個(gè)變量標(biāo)記為true诉濒,
         * 在下一個(gè)Message進(jìn)隊(duì)時(shí)會(huì)判斷這個(gè)message的位置周伦,如果在隊(duì)首就會(huì)調(diào)用nativeWake()方法喚醒線程!
         */
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

在來看看消息入隊(duì)列:

 Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; /
    int nextPollTimeoutMillis = 0;
     // 不斷循環(huán)取出消息
    for (; ; ) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
         //掛起未荒,即阻塞
        nativePollOnce(ptr, nextPollTimeoutMillis);
        // 為什么加synchronized专挪?
        //因?yàn)橄㈥?duì)列內(nèi)部維護(hù)的是單鏈表,線程不安全片排,消息入隊(duì)列可能同時(shí)有消息出隊(duì)列
        // 那么消息出隊(duì)列可能會(huì)把消息移除狈蚤,還有一種情況就是入隊(duì)時(shí)可能會(huì)造成順序錯(cuò)亂
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            // 同步屏障點(diǎn),優(yōu)先處理所有的異步消息
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                /**
                 *
                 * 1划纽、每個(gè)入MessageQueue之前是都有出發(fā)時(shí)間when脆侮,是按照出發(fā)使勁按從小到大排序的,默認(rèn)觸發(fā)時(shí)間為當(dāng)前系統(tǒng)時(shí)間勇劣;
                 * 2靖避、延遲發(fā)送的話:系統(tǒng)時(shí)間 + 延遲時(shí)間潭枣;
                 * 3、MessageQueue輪詢時(shí)會(huì)判斷:如果 觸發(fā)時(shí)間 > 系統(tǒng)時(shí)間 說明時(shí)延遲消息幻捏,暫時(shí)不處理并阻塞對應(yīng)的觸發(fā)時(shí)間減去系統(tǒng)時(shí)間
                 */
                if (now < msg.when) {// 觸發(fā)時(shí)間 > 系統(tǒng)時(shí)間 延遲消息
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else { // 正常消息
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
               /**
                     * 
                     * 1.如果nextPollTimeoutMillis=-1盆犁,一直阻塞不會(huì)超時(shí)。
                     *
                     * 2.如果nextPollTimeoutMillis=0篡九,不會(huì)阻塞谐岁,立即返回。
                     *
                     * 3.如果nextPollTimeoutMillis>0榛臼,最長阻塞nextPollTimeoutMillis毫秒(超時(shí))伊佃,如果期間有程序喚醒會(huì)立即返回。
                     */
                nextPollTimeoutMillis = -1;
            }
            if (mQuitting) {
                dispose();
                return null;
            }


            /**
             * 如果有阻塞(沒有消息了或者只有Delay的消息)沛善,會(huì)把mBlocked這個(gè)變量標(biāo)記為true航揉,
             * 在下一個(gè)Message進(jìn)隊(duì)時(shí)會(huì)判斷這個(gè)message的位置,如果在隊(duì)首就會(huì)調(diào)用nativeWake()方法喚醒線程金刁!
             * 也就是在enqueueMessage方法中調(diào)用nativeWake()方法喚醒線程帅涂!
             * 注意:nativePollOnce 方法有兩種喚醒方式:1、超時(shí)尤蛮;2媳友、nativeWake方法*/
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }


            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

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

注釋已經(jīng)很清楚了

前面講到Handler創(chuàng)建時(shí)會(huì)取出當(dāng)前線程的Looper,并獲取Looper中的MessageQueue产捞,然后發(fā)消息到MessageQueue庆锦,通過Looper不斷輪訓(xùn)取出消息交給Handler處理,看看Looper是如何創(chuàng)建轧葛?

//構(gòu)造方法
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

// Looper 創(chuàng)建
 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));
}

Looper的構(gòu)造方法是private的搂抒,Looper通過 Looper.prepare()方法創(chuàng)建,Looper創(chuàng)建成功并且將Looper實(shí)例保存在ThreadLocal中尿扯,在Looper創(chuàng)建時(shí)求晶,會(huì)創(chuàng)建一個(gè)MessageQueue消息隊(duì)列衷笋。

調(diào)用Looper.loop()方法開始輪訓(xùn)同步取出消息交給Handler處理芳杏;

loop
 public static void loop() {
    //獲取當(dāng)前線程綁定的Looper,如果為null則拋出異常辟宗,否則取出MessageQueue
    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;
    // 開始不斷輪訓(xùn)取出消息
    for (; ; ) {
        Message msg = queue.next(); // MessageQueue 可能會(huì)被掛起或阻塞爵赵,底層管道發(fā)通知
        // 如果消息不為null,調(diào)用dispatchMessage分發(fā)消息
        if (msg == null) { return; }
        msg.target.dispatchMessage(msg);
        //把消息放入消息池中
        msg.recycleUnchecked();
    }
}
loop
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();  //獲取與當(dāng)前線程綁定的Looper
}
Handler中的dispatchMessage方法對消息進(jìn)行分發(fā)
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
Message中的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) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

ThreadLocal

Looper是存放在ThreadLocal中泊脐,ThreadLocal的實(shí)例代表了一個(gè)線程局部的變量空幻,每條線程都只能看到自己的值,并不會(huì)意識(shí)到其它的線程中也存在該變量容客。它采用采用空間來換取時(shí)間的方式秕铛,解決多線程中相同變量的訪問沖突問題约郁,所以Looper使用ThreadLocal保存在當(dāng)前線程中,其他線程對它不可見但两,這樣就解決了鬓梅,每個(gè)線程只有一個(gè)Looper實(shí)例。

 public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

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

ThreadLocal作為Looper的靜態(tài)成員變量谨湘,即Looper全局僅僅只有一個(gè)ThreadLocal绽快,而存放Looper對象的并不是TheradLocal,而是ThreadLocalMap紧阔,ThreadLocalMap是ThreadLocal的內(nèi)部類是一個(gè)自定義的HashMap坊罢,而ThreadLocalMap又作為Thread類的成員成員變量即每一個(gè)Thread的實(shí)例僅有一個(gè)ThreadLocalMap,所以總結(jié)就是ThreadLocal僅僅是使用了裝飾模式封裝了ThreadLocalMap并對ThreadLocalMap的功能進(jìn)行了增強(qiáng)寓辱,中一個(gè)過程就是,Looper調(diào)用ThreadLocal赤拒,然后ThreadLocal通過Thread得到當(dāng)前線程的ThreadLocalMap秫筏,并將Looper作為value存儲(chǔ)到ThreadLocalMap中而Key是ThreadLocal。

每個(gè)Thread的對象都有一個(gè)ThreadLocalMap挎挖,而ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類这敬,當(dāng)創(chuàng)建一個(gè)ThreadLocal的時(shí)候,就會(huì)將該ThreadLocal對象作為Key添加到該Map中蕉朵,其中Key就是ThreadLocal崔涂,值可以是Looper。
在該類中始衅,我覺得最重要的方法就是兩個(gè):set()和get()方法冷蚂。當(dāng)調(diào)用ThreadLocal的get()方法的時(shí)候,會(huì)先找到當(dāng)前線程的ThreadLocalMap汛闸,然后再找到對應(yīng)的值蝙茶。set()方法也是一樣。

下面貼出一張Looper保存到TheadLocal中的時(shí)序圖:


looper_and_threadlocal.png

回答問題:

  • Handler消息機(jī)制的流程是什么诸老?(文章首部有圖)
    1隆夯、Handler創(chuàng)建的時(shí)候會(huì)獲取當(dāng)前線程的Looper;
    2别伏、通過Handler通過Looper獲取MessageQueuer發(fā)消息蹄衷;
    3、Looper不斷輪訓(xùn)取出消息交給Handler處理厘肮。

  • Handler如何處理延遲消息愧口?
    1、每個(gè)消息都有觸發(fā)時(shí)間when类茂,消息進(jìn)入隊(duì)列是按照觸發(fā)時(shí)間按從小到大排序的调卑;
    2抡砂、默認(rèn)觸發(fā)時(shí)間為當(dāng)前系統(tǒng)時(shí)間,延遲發(fā)送的:系統(tǒng)時(shí)間 + 延遲時(shí)間恬涧;
    3注益、MessageQueue輪詢時(shí)會(huì)判斷:如果觸發(fā)時(shí)間 > 系統(tǒng)時(shí)間 說明時(shí)延遲消息暫時(shí)不處理并阻塞對應(yīng)的觸發(fā)時(shí)間減去系統(tǒng)時(shí)間;
    4溯捆、Handler發(fā)消息進(jìn)入隊(duì)列時(shí)是根據(jù)時(shí)間wahen的消息排序的丑搔,在取消息時(shí)根據(jù)當(dāng)前系統(tǒng)時(shí)間對比,如果系統(tǒng)時(shí)間now < when,說明是延遲消息提揍,
    那么 when - now = nextPollTimeoutMillis啤月,調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)進(jìn)行掛起(底層的技術(shù)),
    注意:nativePollOnce有超時(shí)換新劳跃,當(dāng)然在消息入隊(duì)列也會(huì)判斷是否喚醒nativeWake(mPtr)

  • 為什么消息入隊(duì)列和消息出隊(duì)列會(huì)加同步代碼塊谎仲?會(huì)出現(xiàn)呢什么問題?
    1刨仑、因?yàn)橄㈥?duì)列內(nèi)部維護(hù)的是單鏈表郑诺,是線程不安全的,
    2杉武、消息入隊(duì)列可能同時(shí)有消息出隊(duì)列 那么消息出隊(duì)列可能會(huì)把消息移除辙诞,還有一種情況就是入隊(duì)時(shí)可能會(huì)造成順序錯(cuò)亂。

  • 為什么兩個(gè)鎖在兩個(gè)不同方法的同步代碼塊能起到同步轻抱?
    1飞涂、實(shí)際上是同一個(gè)對象,即MessageQueue祈搜,所以他們是同一個(gè)監(jiān)視器较店。
    如果想更進(jìn)一步了解synchronized請移步Android 并發(fā)之synchronized鎖住的是代碼還是對象(二)介紹

  • 消息時(shí)如何進(jìn)入隊(duì)列的?
    1容燕、消息隊(duì)列內(nèi)部維護(hù)一個(gè)單鏈表泽西,消息進(jìn)入隊(duì)列時(shí)是根據(jù)時(shí)間wahen的消息排序的,從小到大排序

  • 消息屏障也就是msg.target == null的消息的作用是什么或者如何處理的缰趋?
    1捧杉、消息屏障是為了區(qū)別同步消息和異步消息的優(yōu)先級;
    2秘血、在Handler中異步消息優(yōu)先處理味抖,而我們平時(shí)發(fā)送的消息全是同步消息,一般異步消息系統(tǒng)發(fā)的灰粮,如:系統(tǒng)發(fā)送更新UI的消息仔涩,這樣能夠在第一時(shí)間更新UI。

  • 為什么在子線程中創(chuàng)建Handler會(huì)crach粘舟,在主線程中就不會(huì)發(fā)生呢熔脂?
    1佩研、這個(gè)問題如果要展開來講,就是一篇文章霞揉,為了回答這個(gè)問題旬薯,我先上ActivityThread部分代碼:

    public static void main(String[] args) {
      // ...此處省略一萬行代碼
    
     // 準(zhǔn)備主線程的 Looper
      Looper.prepareMainLooper();
    
       // 創(chuàng)建 ActivityThread實(shí)例,用于管理應(yīng)用程序進(jìn)程中主線程的執(zhí)行
      ActivityThread thread = new ActivityThread();
    
        // 進(jìn)入 attach 方法
      thread.attach(false, startSeq)适秩;
    
       // ...此處省略一萬行代碼
    
       // 開始 Looper
      Looper.loop();
    

    }

大家都知道App啟動(dòng)一般都是先進(jìn)入ActivityThread的 main 方法绊序,看到Handler的身影。在安卓中都是以消息進(jìn)行驅(qū)動(dòng)秽荞,在這里也不例外骤公,我們可以看到先進(jìn)行 Looper 的準(zhǔn)備,在最后開啟 Looper 進(jìn)行輪訓(xùn)獲取消息扬跋,用于處理傳到主線程的消息阶捆。這也是為什么我們在主線程不需要先進(jìn)行 Looper 的準(zhǔn)備和開啟而子線程則必須要進(jìn)行 Looper 的準(zhǔn)備和開啟。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钦听,一起剝皮案震驚了整個(gè)濱河市洒试,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彪见,老刑警劉巖儡司,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娱挨,死亡現(xiàn)場離奇詭異余指,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)跷坝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門酵镜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人柴钻,你說我怎么就攤上這事淮韭。” “怎么了贴届?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵靠粪,是天一觀的道長。 經(jīng)常有香客問我毫蚓,道長占键,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任元潘,我火速辦了婚禮畔乙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翩概。我一直安慰自己牲距,他們只是感情好返咱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著牍鞠,像睡著了一般咖摹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上皮服,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天楞艾,我揣著相機(jī)與錄音,去河邊找鬼龄广。 笑死硫眯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的择同。 我是一名探鬼主播两入,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼敲才!你這毒婦竟也來了裹纳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤紧武,失蹤者是張志新(化名)和其女友劉穎剃氧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阻星,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朋鞍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妥箕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滥酥。...
    茶點(diǎn)故事閱讀 39,754評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖畦幢,靈堂內(nèi)的尸體忽然破棺而出坎吻,到底是詐尸還是另有隱情,我是刑警寧澤宇葱,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布瘦真,位于F島的核電站,受9級特大地震影響黍瞧,放射性物質(zhì)發(fā)生泄漏诸尽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一雷逆、第九天 我趴在偏房一處隱蔽的房頂上張望弦讽。 院中可真熱鬧,春花似錦、人聲如沸往产。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仿村。三九已至锐朴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔼囊,已是汗流浹背焚志。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留畏鼓,地道東北人酱酬。 一個(gè)月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像云矫,于是被迫代替她去往敵國和親膳沽。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評論 2 354

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