大廠面試秘籍—— 深入理解 Handler

概述

Android 的消息機(jī)制主要指的是 Handler 的運(yùn)行機(jī)制坚弱,從開發(fā)者的角度來說 Handler 是 Android 消息機(jī)制的上層接口,而底層的邏輯則是由 MessageQueue戚绕、 Looper 來完成的。

Handler 的設(shè)計(jì)目的是為了解決不能在 Android 主線程中做耗時(shí)操作而又只有主線程才能訪問 UI 的矛盾箫爷。通過 Handler 消息機(jī)制可以讓開發(fā)者在子線程中完成耗時(shí)操作的同時(shí)在主線程中更新UI场绿。

Handler 機(jī)制是 Android 用于 UI 刷新的一套消息機(jī)制啊犬。開發(fā)者可以使用這套機(jī)制達(dá)到線程間通信灼擂、線程切換目的。

這里要思考一個(gè)問題:為什么 Android 非要規(guī)定只有主線程才能更新 UI 呢觉至?

因?yàn)?Android 的所有 View 控件都不是線程安全的剔应,如果在多線程中并發(fā)訪問很可能造成意想不到的結(jié)果。對(duì)于加鎖這種方案也不可取,首先加鎖之后會(huì)讓 UI 訪問邏輯變的很復(fù)雜峻贮,開發(fā)者需要時(shí)刻考慮多線程并發(fā)將會(huì)帶來的問題席怪,其次鎖機(jī)制太重了它會(huì)嚴(yán)重影響 UI 訪問效率。介于這兩個(gè)缺點(diǎn)纤控,最簡(jiǎn)單且高效的方法就是采用單線程的方式訪問 UI挂捻。Handler 機(jī)制應(yīng)運(yùn)而生。

那么 Handler 內(nèi)部是如何完成線程切換的呢船万?答案就是神奇的 :ThreadLocal

ThreadLocal

ThreadLocal 并不是 Thread 刻撒,他的特點(diǎn)很有意思: 每一個(gè)線程存儲(chǔ)的值是相互隔離的

public class TreadLocalDemo {
    // 就算設(shè)置為 static 結(jié)果也是一樣的
    ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>();

    public void runDemo() {
        mThreadLocal.set(true);
        System.out.println(Thread.currentThread().getName() + "  " + mThreadLocal.get());
        new Thread("Thread#1") {
            @Override
            public void run() {
                super.run();
                mThreadLocal.set(false);
                System.out.println(Thread.currentThread().getName() + "  " + mThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                super.run();
                System.out.println(Thread.currentThread().getName() + "  " + mThreadLocal.get());
            }
        }.start();
        System.out.println(Thread.currentThread().getName() + "  " + mThreadLocal.get());
    }
}

運(yùn)行的結(jié)果很清晰的展示他的特點(diǎn),雖然在主線程和線程1中都做了賦值操作耿导,但并不能改變?cè)瓉砭€程的賦值情況声怔。

file

對(duì)于 ThreadLocal 的原理簡(jiǎn)單來說:每一線程都有一個(gè)專門用于保存 ThreadLocal 的成員變量 localValues。 盡管在不同線程中訪問同一個(gè) ThreadLocal 的 setget 方法舱呻,但所做的操作都僅限制于各自線程的內(nèi)部醋火。這就是 ThreadLocal 可以在多個(gè)線程中互不干擾的存儲(chǔ)和讀取數(shù)據(jù)的原因。正是這種特性讓 Handler 做到了線程的切換箱吕。

Looper 正是借助 ThreadLocal 的特點(diǎn)在不同的線程創(chuàng)建不同的實(shí)例芥驳。至此 Handler 與 Looper 、線程達(dá)到了一一對(duì)應(yīng)的綁定關(guān)系殖氏。所以無(wú)論此 Handler 的實(shí)例在什么線程調(diào)用晚树,最終的回調(diào)都會(huì)分發(fā)到創(chuàng)建線程。

MessageQueue

MessageQueue 主要有兩個(gè)操作:插入和讀取雅采。讀取操作也會(huì)伴隨著刪除爵憎。插入和讀取的方法分別對(duì)應(yīng)的是:enquequeMessagenext,MessageQueue 并不是像名字一樣使用隊(duì)列作為數(shù)據(jù)結(jié)構(gòu)婚瓜,而是使用單鏈表來維護(hù)消息宝鼓。單鏈表在插入和刪除上比較有優(yōu)勢(shì)。

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) {
            // 可見只有在調(diào)用 quit() 方法之后才會(huì)返回空
            return null;
        } 
        
   ......
          
        // 一個(gè)死循環(huán) 愚铡!
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // 一個(gè) native 方法,此方法在沒有消息或者消息沒有到執(zhí)行時(shí)間的時(shí)候會(huì)讓線程進(jìn)入等待狀態(tài)胡陪。
            // 有點(diǎn)類似于 Object.wait 但是 nativePollOnce 可以自定等待時(shí)間
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
   ......
                     if (!keep) {
                    synchronized (this) {
                      // 獲取消息后從列表中移除
                        mIdleHandlers.remove(idler);
                    }
                }
        }
    }

最關(guān)鍵的是三點(diǎn)內(nèi)容

  1. 死循環(huán)
  2. nativePollOnce()
  3. 獲取到消息之后從列表中移除

nativePollOnce 是一個(gè) native 方法沥寥,如果單列表中沒有消息或者等待的時(shí)間沒有到,那么當(dāng)前線程將會(huì)被設(shè)置為 **wait 等待狀態(tài) **柠座,直到可以獲取到下一個(gè) Message 邑雅。更詳細(xì)的內(nèi)容可以參見 StackOverflow 上關(guān)于 nativePollOnce的回答而這個(gè)死循環(huán)的目的就是不讓 next方法退出,等待 nativePollOnce 的響應(yīng)妈经。等到獲取到消息之后再將這個(gè)消息從消息列表中移除淮野。

enqueueMessage()

enqueueMessage 方法的主要工作就是向單鏈表中插入數(shù)據(jù)捧书,當(dāng)線程處于等待狀態(tài)則調(diào)用 nativeWake 喚醒線程,讓 next 方法處理消息骤星。

    boolean enqueueMessage(Message msg, long when) {
    ......
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

如何處理延時(shí)消息

詳情請(qǐng)參見Handler是怎么做到消息延時(shí)發(fā)送的 下面再抄一部分結(jié)論:

在 next 方法中如果頭部的這個(gè) Message 是有延遲而且延遲時(shí)間沒到的(now < msg.when)经瓷,會(huì)計(jì)算一下時(shí)間(保存為變量 nextPollTimeoutMillis), 然后在循環(huán)開始的時(shí)候判斷如果這個(gè) Message 有延遲,就調(diào)用nativePollOnce (ptr, nextPollTimeoutMillis)進(jìn)行阻塞洞难。nativePollOnce()的作用類似與 Object.wait(), 只不過是使用了 Native 的方法對(duì)這個(gè)線程精確時(shí)間的喚醒舆吮。

  1. postDelay()一個(gè)10秒鐘的 Runnable A、消息進(jìn)隊(duì)廊营,MessageQueue 調(diào)用nativePollOnce()阻塞歪泳,Looper 阻塞;
  2. 緊接著 post() 一個(gè) Runnable B露筒、消息進(jìn)隊(duì)呐伞,判斷現(xiàn)在A時(shí)間還沒到、正在阻塞慎式,把B插入消息隊(duì)列的頭部(A的前面)伶氢,然后調(diào)用 nativeWake()方法喚醒線程;
  3. MessageQueue.next() 方法被喚醒后瘪吏,重新開始讀取消息鏈表癣防,第一個(gè)消息B無(wú)延時(shí),直接返回給 Looper
  4. Looper 處理完這個(gè)消息再次調(diào)用 next() 方法掌眠,MessageQueue 繼續(xù)讀取消息鏈表蕾盯,第二個(gè)消息A還沒到時(shí)間,計(jì)算一下剩余時(shí)間(假如還剩 9秒)繼續(xù)調(diào)用 nativePollOnce()阻塞蓝丙;直到阻塞時(shí)間到或者下一次有Message 進(jìn)隊(duì)级遭;
file

Looper

Looper 在 Android 消息機(jī)制中扮演著消息循環(huán)的角色。具體來說他的任務(wù)就是不停的從 MessageQueue 中獲取消息渺尘,如果有新消息就立即處理挫鸽,沒有消息的時(shí)候,與 Looper 綁定的線程就會(huì)被 MessageQueue 的 next 的 nativePollOne 方法置于等待狀態(tài)鸥跟。

Looper 是如何創(chuàng)建的

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 創(chuàng)建 Looper 實(shí)例丢郊,將實(shí)例保存在 sThreadLocal 中與當(dāng)前線程綁定。
    sThreadLocal.set(new Looper(quitAllowed));
}

在構(gòu)造方法里面他會(huì)創(chuàng)建一個(gè) MessageQueque医咨,并保存當(dāng)前線程枫匾。

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

getMainLooper 可以在任何地方獲取到主線程的 Looper,那么主線程是如何創(chuàng)建 Looper 的呢拟淮?

主線程創(chuàng)建 Looper 的過程 —— AndroidThread

我們的目光來到了 AndroidThread 類干茉, 在 AndroidThread 中我們看到了熟悉的方法 :main(String[] args。千萬(wàn)不要被 AndroidThread 的名字所迷惑惩歉,AndroidThread 并不是一個(gè)線程等脂,它只是一個(gè)開啟主線程的類。

public static void main(String[] args) {
        ....

        // 創(chuàng)建 Looper 和 MessageQueue 對(duì)象撑蚌,用于處理主線程的消息
        Looper.prepareMainLooper();

        // 創(chuàng)建 ActivityThread 對(duì)象
        ActivityThread thread = new ActivityThread(); 

        // 建立 Binder 通道 (創(chuàng)建新線程)
        thread.attach(false);

        // 消息循環(huán)運(yùn)行
        Looper.loop(); 
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

注意調(diào)用的是 prepare(false) 不允許退出上遥,這是為什么呢?

    public static void prepareMainLooper() {
       // 不允許退出
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

這是因?yàn)橹骶€程的 Looper 伴隨著一個(gè) App 的整個(gè)生命周期争涌,所有的 UI訪問粉楚、View 刷新都是在 Looper 里面完成的,如果允許開發(fā)者手動(dòng)退出亮垫,那么整個(gè) App 都會(huì)變得不可控模软。

更多細(xì)節(jié)可以參見下面的一節(jié)「 Looper中的死循環(huán)為什么沒有卡死線程」

Looper 是如何運(yùn)行的

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
  ......
    
    final MessageQueue queue = me.mQueue;
  ......

    // 死循環(huán)
    for (;;) {
       // 可能會(huì)被阻塞
        Message msg = queue.next();
        if (msg == null) {
            // msg 為 null 會(huì)立即退出循環(huán),這也是退出循環(huán)的唯一方法饮潦。
            return;
        }
   ......
  
        try {
          // 開始分發(fā)消息
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
   ......
    }
}

loop 方法是一個(gè)死循環(huán)燃异,他的工作就是不斷的檢查 MessageQueue 是否有可以處理的消息,如果有這將消息分發(fā)給 Handler 處理继蜡。既然是死循環(huán)那么為什么沒有卡死線程呢回俐?更多細(xì)節(jié)可以參見下面的一節(jié)「 Looper中的死循環(huán)為什么沒有卡死線程」

Looper 如何退出

Looper 內(nèi)部提供了兩種退出的方法,分別是 quit稀并、quitSafely仅颇。從本質(zhì)上來講 quit 調(diào)用后會(huì)立即退出 Looper,而 quitSafely 只是設(shè)定一個(gè)退出標(biāo)記碘举,等待消息隊(duì)列中的已有消息處理完畢后忘瓦,再退出。

Looper 退出后引颈,通過 Handler 發(fā)送的消息會(huì)失敗耕皮,這個(gè)時(shí)候 Handler send 方法會(huì)返回 false。在子線程中线欲,如果手動(dòng)為其創(chuàng)建了 Looper明场,那么在所有的邏輯完成后理應(yīng)手動(dòng)調(diào)用 quit 方法終止 Looper 內(nèi)部的循環(huán),否則這個(gè)子線程會(huì)一直處于等待狀態(tài)李丰,而退出 Looper 之后苦锨,子線程也就會(huì)隨之終止,因此在子線程中使用 Looper趴泌,==必須在恰當(dāng)?shù)臅r(shí)機(jī)終止它==舟舒。

/**
* Quits the looper.
*/
public void quit() {
    mQueue.quit(false);
}

/**
 * Quits the looper safely.
 */
public void quitSafely() {
    mQueue.quit(true);
}

如果是主線程開發(fā)者就退出不了,要是退出了嗜憔,就麻煩大了秃励。

public static void prepareMainLooper() {
  // fasle 不允許退出
    prepare(false);
 ....
}

退出的本質(zhì)

Looper.quit 的源碼中可以清晰看到,本質(zhì)上調(diào)用的是 MessageQueue 的 quite 方法吉捶。而在調(diào)用 MessageQueue.quite 之后 再次調(diào)用 MessageQueue.next()會(huì)返回 null

 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) {
            // 可見只有在調(diào)用 quit() 方法之后才會(huì)返回空
            return null;
        } 
        
   ......

Looper.loop()在調(diào)用 queue.next()得的結(jié)果為 null 的時(shí)候會(huì)立即跳出死循環(huán), 這也是退出死循環(huán)的唯一方式夺鲜。

public static void loop() {
……
    for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
……

Looper中的死循環(huán)為什么沒有卡死線程

參考知乎:Android中為什么主線程不會(huì)因?yàn)長(zhǎng)ooper.loop()里的死循環(huán)卡死皆尔?

我們都知道:一個(gè)簡(jiǎn)單的死循環(huán)會(huì)消耗掉大量資源導(dǎo)致線程被卡死。但是 Looper.loop() 方法開啟就是一個(gè)死循環(huán)币励,它為什么沒有卡死線程呢慷蠕?總結(jié)一下主要有3個(gè)疑惑:

  • Looper 為什么要使用死循環(huán)
  • Android 的主線程為什么沒有被 Looper 中的死循環(huán)卡死
  • 喚醒主線程 Looper 的消息從何而來

Looper 為什么要使用死循環(huán)

首先要說說的是為什么在 Looper 中使用死循環(huán)。在 CPU 看來操作系統(tǒng)線程(這里的定義可以參見《Java基礎(chǔ)》多線程和線程同步 —— 進(jìn)程與線程一節(jié) ) 只不過是一段可以執(zhí)行的代碼食呻,CPU 會(huì)使用 CFS 調(diào)度算法流炕,保證每一個(gè) task 都盡可能公平的享用 CPU 時(shí)間片。既然操作系統(tǒng)線程是一段可以執(zhí)行的代碼仅胞,當(dāng)可執(zhí)行的代碼結(jié)束之后每辟,線程生命周期也就終止,線程將會(huì)退出干旧。但是對(duì)于 Android 這類的 GUI 程序渠欺,我們絕對(duì)不希望代碼執(zhí)行一段時(shí)間之后主線程就自己停止掉,那么如何保證線程一直執(zhí)行下去呢椎眯?簡(jiǎn)單的做法就是在線程中執(zhí)行死循環(huán)峻堰,讓線程一直工作下去不會(huì)停止退出。

總的來說盅视,在線程中使用死循環(huán)想要解決的問題就是防止線程自己退出捐名。所以對(duì)于 Looper 而言,他的死循環(huán)就是希望不斷的從 MessageQueue 中獲取消息闹击,而不希望線程線性執(zhí)行之后就退出镶蹋。

Android 的主線程為什么沒有被 Looper 中的死循環(huán)卡死

首先 Android 所有的 UI 刷新和生命周期的回調(diào)都是由 Handler消息機(jī)制完成的,就是說 UI 刷新和生命周期的回調(diào)都是依賴 Looper 里面的死循環(huán)完成的赏半,這樣設(shè)計(jì)的目的上文已經(jīng)闡述清楚贺归。這篇文章里面貼了 AndroidTread 對(duì)于 Handler 的實(shí)現(xiàn)類 H 的源碼(進(jìn)入文章后搜索:內(nèi)部類H的部分源碼) 源碼太長(zhǎng),我就不貼了断箫。

其次Looper 不是一直拼命干活的傻小子拂酣,而是一個(gè)有活就干沒活睡覺的老司機(jī),所以主線程的死循環(huán)并不是一直占據(jù)著 CPU 的資源不釋放仲义,不會(huì)造成過度消耗資源的問題婶熬。這里涉及到了Linux pipe/epoll機(jī)制,簡(jiǎn)單說就是在主線程的 MessageQueue 沒有消息時(shí)埃撵,便在 loop 的 queue.next() 中的 nativePollOnce() 方法里讓線程進(jìn)入休眠狀態(tài)赵颅,此時(shí)主線程會(huì)釋放CPU資源,直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生才會(huì)再次被喚醒暂刘。所以 Looper 里的死循環(huán)饺谬,沒有一直空輪詢著瞎忙,也不是進(jìn)入阻塞狀態(tài)占據(jù)著 CPU 不釋放谣拣,而是進(jìn)入了會(huì)釋放資源的等待狀態(tài)募寨,等待著被喚醒

經(jīng)過上面的討論可以得知:

  1. Looper 中的死循環(huán)是 Android 主線程刷新 UI 和生命周期回調(diào)的基石族展。
  2. Looper 中的死循環(huán)會(huì)根據(jù)消息分別進(jìn)入等待和喚醒狀態(tài),并不會(huì)一直持有資源拔鹰,所以就不會(huì)有卡死的問題苛谷。

那么喚醒 Looper 的消息是從哪里來的呢?

喚醒主線程 Looper 的消息從何而來

目光回到 AndroidThread 類中的這幾行代碼

public static void main(String[] args) {
        ....
          
        // 創(chuàng)建ActivityThread對(duì)象
        ActivityThread thread = new ActivityThread(); 
  
        //建立Binder通道 (創(chuàng)建新線程)
        thread.attach(false);

        Looper.loop(); //消息循環(huán)運(yùn)行
    }

在創(chuàng)建 ActivityThread 后會(huì)通過thread.attach(false)方法在 ActivityThread 中創(chuàng)建 Binder 的服務(wù)端用于接收系統(tǒng)服務(wù)AMS發(fā)送來的事件格郁,然后通過 ActivityThread 的內(nèi)部類 ApplicationThread 中 sendMessage 方法

......

public final void scheduleStopActivity(IBinder token, boolean showWindow,
                int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

        public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
            sendMessage(
                showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
                token);
        }

        public final void scheduleSleeping(IBinder token, boolean sleeping) {
            sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
        }

        public final void scheduleResumeActivity(IBinder token, int processState,
                boolean isForward, Bundle resumeArgs) {
            updateProcessState(processState, false);
            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
......

將消息發(fā)送給 AndroidThread 的 Handler 實(shí)現(xiàn)內(nèi)部類 H。從而完成了 Binder Thread 到 UI 線程即主線程的切換独悴,喚醒 Looper 進(jìn)行 dispatchMessage 的動(dòng)作例书。

喚醒的具體操作參見上文「MessageQueue -> enqueueMessage -> nativeWake」

拓展:如何在非主線程中使用 Handler 消息機(jī)制

通過 ActivityThread 的源碼可以清楚看到

public static void main(String[] args) {
        ....

        //創(chuàng)建Looper和MessageQueue對(duì)象,用于處理主線程的消息
        Looper.prepareMainLooper();
        ....

        Looper.loop(); //消息循環(huán)運(yùn)行
        ....
    }

Android 在啟動(dòng)一個(gè) App 的時(shí)候都會(huì)創(chuàng)建一個(gè) Looper刻炒,而用戶啟動(dòng)子線程的時(shí)候是沒有這個(gè)操作的决采,所以需要開發(fā)者自己創(chuàng)建并調(diào)用 Looper.loop() 讓 Looper 運(yùn)行起來。

   new Thread("Thread#1") {
      @Override
      public void run() {
         // 手動(dòng)生成為當(dāng)前線程生成 Looper
         Looper.prepare();
         Handler handler = new Handler();
         Looper.loop();
      }

    }.start();

此處我們做個(gè)實(shí)驗(yàn)坟奥,既然 Looper 是個(gè)死循環(huán)那么在 loop() 之后的代碼是不是永遠(yuǎn)沒有機(jī)會(huì)執(zhí)行呢树瞭?

/**
 * Android 消息機(jī)制 —— Handler
 * <p>
 * Created by im_dsd on 2019-09-07
 */
public class HandlerDemo {

    public static final String TAG = "HandlerDemo";
    private Handler mHandler;
    private Looper mLooper;

    /**
     * 如何在子線程中開啟 Handler
     */
    public void startThreadHandler() {
        new Thread("Thread#1") {
            @Override
            public void run() {
                // 手動(dòng)生成為當(dāng)前線程生成 Looper
                Looper.prepare();
                mLooper = Looper.myLooper();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.d(TAG,Thread.currentThread().getName() + "  " + msg.what);
                    }
                };
                Log.d(TAG,Thread.currentThread().getName() + "loop 開始 會(huì)執(zhí)行嗎?  ");
                // 手動(dòng)開啟循環(huán)
                Looper.loop();
                Log.d(TAG,Thread.currentThread().getName() + "loop 結(jié)束 會(huì)執(zhí)行嗎爱谁?  ");
            }

        }.start();

        // 等待線程啟動(dòng)
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d(TAG,"start send message");
        mHandler.sendEmptyMessage(1);
        mHandler.post(() -> Log.d(TAG,Thread.currentThread().getName()));
    }
}

自啟動(dòng)到將 App 徹底殺死晒喷,輸出結(jié)果也是如此:loop 后面的代碼沒有執(zhí)行!

2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 開始 會(huì)執(zhí)行嗎访敌?  
2964-2964/com.example.dsd.demo D/HandlerDemo: start send message
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1  1
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1

這意味兩個(gè)嚴(yán)重的問題:looper() 后面的代碼一直都不會(huì)執(zhí)行而且線程 Thread#1 將會(huì)一直運(yùn)行下去凉敲!在 JVM 規(guī)范里面規(guī)定==處于運(yùn)行中的線程會(huì)不被 GC==。在沒有消息的時(shí)候 Looper 會(huì)處于等待狀態(tài)寺旺。等待在 Thread 的生命周期里仍然屬于運(yùn)行狀態(tài)爷抓,它永遠(yuǎn)不會(huì)被 GC

所以很多網(wǎng)上很多文章里都有一個(gè)致命的缺陷阻塑,根本就沒有提及到要在使用完畢后即使退出 Looper蓝撇。緊接上文的代碼

      // 嘗試 1秒 后停止
        try {
            Thread.sleep(1000);
            mLooper.quit();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

此時(shí)的結(jié)果

2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 開始 會(huì)執(zhí)行嗎?  
2964-2964/com.example.dsd.demo D/HandlerDemo: start send message
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1  1
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1
2964-3007/com.example.dsd.demo D/HandlerDemo: Thread#1loop 結(jié)束 會(huì)執(zhí)行嗎陈莽?  

根據(jù)綜上所述渤昌,Handler 機(jī)制完全可以在 Android 中用作線程間的消息同步,這里要強(qiáng)調(diào)一下走搁,Handler 機(jī)制是 Android 獨(dú)有的耘沼,筆者在寫上面的 Demo 的時(shí)候竟然傻傻的將 Handler 的啟動(dòng)放在了 Java 中,直接拋出了 RuntimException Stub 的錯(cuò)誤朱盐。

總結(jié)一下在子線程中使用 Handler 機(jī)制要注意兩點(diǎn)問題:

  1. 必須調(diào)用 Looper.prepare();手動(dòng)生成為當(dāng)前線程生成 Looper群嗤,并調(diào)用Looper.looper()啟動(dòng)內(nèi)部的死循環(huán)。
  2. 必須在使用結(jié)束后調(diào)用 Looper.myLooper().quit()退出當(dāng)前線程兵琳。

Handler

Handler 的工作主要就是發(fā)送和接收消息狂秘。消息的發(fā)送可以通過 post 的一系列方法和 send 的一系類方法骇径。在創(chuàng)建 Handler 的時(shí)候他會(huì)默認(rèn)使用當(dāng)前線程的 Looper ,如果當(dāng)前線程沒有創(chuàng)建過 Looper 會(huì)拋出如下異常者春。

file

當(dāng)然也可以手動(dòng)指定不同線程的 Looper破衔。

Handler mHandler = new Handler(Looper.getMainLooper());

消息是如何發(fā)送到的呢?

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageAtTime(msg, uptimeMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

經(jīng)過一系列的跟蹤钱烟,最終的結(jié)果是調(diào)用了enqueueMessage(MessageQueue, Message, long)方法晰筛,目的就是為了向 MessageQueue 中插入一條消息。

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

而后 nativeWake 將會(huì)喚醒等待的線程拴袭,MessageQueue#next將會(huì)在Looper.loop()中將這條消息返回读第,Looper.loop()在收到這條消息之后最終會(huì)交由 Handler#dispatchMessage處理

/** 
 * Looper 的 loop 方法
 */
public static void loop() {
  ......

    // 死循環(huán)
    for (;;) {
   ......
      // 開始分發(fā)消息  msg.target 指的就是發(fā)送消息的 Handler
       msg.target.dispatchMessage(msg);
   ......
    }
}
   /**
     * Handle 的 dispatchMessage 方法
     */
    public void dispatchMessage(Message msg) {
       // 首先檢查 msg 的 callback 是否為 null
        if (msg.callback != null) {
          // 不為 null 使用 msg 的 callback 處理消息
            handleCallback(msg);
        } else {
            // mCallback 是否為 null
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
          // 都沒有指定則交由開發(fā)者重寫的 handleMessage 處理
            handleMessage(msg);
        }
    }

從上面的邏輯我們可以看出 callback 的優(yōu)先級(jí):msg#callback > new Handler(Callback) 中 指定的 Callback> 重寫 Handler 的 callBack

mCallback指的是一個(gè)接口 , 可以使用 Handler handler = new Handler(Callback)的方式指定回調(diào),這種方式可以由外部傳遞進(jìn)來會(huì)回調(diào)方法拥刻,更加靈活怜瞒。

 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 */
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

至此對(duì)于 Android 的消息機(jī)制已經(jīng)講解完畢,你是否已經(jīng)有了清晰的認(rèn)識(shí)呢般哼?對(duì)于開篇的問題:Handler 是如何完成線程切換的吴汪,你找到答案了嗎?

常見問題分析

為什么不能在子線程中更新 UI 蒸眠,根本原因是什么漾橙?

16c0641ad370a90a.jpeg
file

mThread 是主線程,這里會(huì)檢查當(dāng)前線程是否是主線程楞卡。

為什么 onCreate 里面沒有進(jìn)行上面的檢查呢近刘?

這個(gè)問題原因出現(xiàn)在 Activity 的生命周期中 , 在 onCreate 方法中, UI 處于創(chuàng)建過程臀晃,對(duì)用戶來說界面還不可見觉渴,直到 onStart 方法后界面可見了,再到 onResume 方法后頁(yè)面可以交互徽惋,從某種程度來講, 在 onCreate 方法中不能算是更新 UI案淋,只能說是配置 UI,或者是設(shè)置 UI 屬性险绘。 這個(gè)時(shí)候不會(huì)調(diào)用到 ViewRootImpl.checkThread () , 因?yàn)?ViewRootImpl 沒有創(chuàng)建踢京。 而在 onResume 方法后, ViewRootImpl 才被創(chuàng)建宦棺。 這個(gè)時(shí)候去交戶界面才算是更新 UI瓣距。

setContentView 知識(shí)建立了 View 樹,并沒有進(jìn)行渲染工作 (其實(shí)真正的渲染工作實(shí)在 onResume 之后)代咸。也正是建立了 View 樹蹈丸,因此我們可以通過 findViewById() 來獲取到 View 對(duì)象,但是由于并沒有進(jìn)行渲染視圖的工作,也就是沒有執(zhí)行 ViewRootImpl.performTransversal逻杖。同樣 View 中也不會(huì)執(zhí)行 onMeasure (), 如果在 onResume() 方法里直接獲取 View.getHeight() / View.getWidth () 得到的結(jié)果總是 0 解決方案是在 UI 真正可見的方法 onWindowFocusChanged() 里面獲取奋岁。

為什么 Handler 構(gòu)造方法里面的 Looper 不是直接 new ?

如果在 Handler 構(gòu)造方法里面直接 new Looper(), 可能是無(wú)法保證 Looper 唯一,只有用 Looper.prepare() 才能保證唯一性荸百,具體可以看 prepare 方法闻伶。

MessageQueue 為什么要放在 Looper 私有構(gòu)造方法初始化?

因?yàn)橐粋€(gè)線程只綁定一個(gè) Looper ,所以在 Looper 構(gòu)造方法里面初始化就可以保證 mQueue 也是唯一的 Thread 對(duì)應(yīng)一個(gè) Looper 對(duì)應(yīng)一個(gè) mQueue够话。

總結(jié)

  1. Android 的消息機(jī)制指的就是 Handler 消息機(jī)制蓝翰,Handler 是面向開發(fā)者的上層接口,而底層的實(shí)現(xiàn)是 MessageQueue女嘲、Looper畜份、ThreadLocal
  2. MessageQueue 使用單鏈表的數(shù)據(jù)結(jié)構(gòu)承載消息,在 next 方法中 nativePollOne 方法會(huì)在消息為空的時(shí)候講線程置為等待狀態(tài)澡为,直到有新的消息到來才會(huì)再次喚醒線程。所以 Looper.loop 雖然是死循環(huán)也不會(huì)卡死景埃。
  3. Looper 的主要任務(wù)是不斷嘗試從 MessageQueue 中獲取消息媒至,為了不讓線程線性執(zhí)行完畢,loop 中開啟了一個(gè)死循環(huán)谷徙。因?yàn)橹骶€程的所有生命周期都是由 Handler 機(jī)制完成的,所以這個(gè)主線程中死循環(huán)不允許開發(fā)者手動(dòng)退出,什么時(shí)候 App 退出了肌蜻,這個(gè)死循環(huán)才會(huì)被退出绊含。而子線程中沒有這機(jī)制,所以在使用完畢后必須手動(dòng)退出屈尼,否者線程會(huì)一直處于等待狀態(tài)册着,不會(huì)被GC。
  4. Handler 是借助 ThreadLocal 機(jī)制完成線程切換的脾歧,Handler 在創(chuàng)建的時(shí)候就已經(jīng)獲取了和線程綁定的 Looper甲捏,所以無(wú)論 Handler 在什么線程調(diào)用,最終都會(huì)回到 Looper 綁定的線程鞭执,所以 Handler 很適合在 Android 中做線程間通信司顿。

參考

  1. 《Android 開發(fā)藝術(shù)探究》第十章
  2. 知乎問答:《Android中為什么主線程不會(huì)因?yàn)長(zhǎng)ooper.loop()里的死循環(huán)卡死》
  3. StackOverflow: 《android - what is message queue native poll once in android?》
  4. 《Handler 是如何做到發(fā)送延時(shí)消息的》

Handler 的再理解與總結(jié)

MessageQueue#next()

這個(gè)方法里面是一個(gè)死循環(huán),但是里面的方法 nativePollOnce 運(yùn)用了 Linux 的 epoll 機(jī)制兄纺,在沒有消息的時(shí)候回會(huì)將線程掛起大溜,注意此時(shí)的掛起相當(dāng)于 Object.wait() : 它會(huì)釋放 CPU 資源,等待喚醒估脆。有消息進(jìn)入的時(shí)候回到用 MessageQueue#enqueueMessage() 加入數(shù)據(jù)钦奋,此時(shí) MessageQueue#enqueueMessage() 內(nèi)部的 nativeWeak 會(huì)重新喚醒線程,

可以發(fā)現(xiàn)

  1. 雖然是死循環(huán)但是他空閑時(shí)間并不消耗資源,死循環(huán)的目是為了防止獲消息的邏輯退出
  2. **Loop#loop() 也是個(gè)死循環(huán)锨苏,但是沒有 message 的時(shí)候同樣會(huì)被 MessageQueue#next 掛起疙教,不會(huì)控輪詢消耗資源 **。
  3. **當(dāng)有消息進(jìn)入的時(shí)候 next 方法會(huì)被立即喚醒伞租,但是是否將消息返回不一定贞谓,要看是不是延時(shí)消息 **。

延時(shí)消息

MessageQueue#next() 中會(huì)判斷消息的時(shí)間葵诈,如果還沒有到消息執(zhí)行的時(shí)間裸弦,會(huì)將消息定時(shí)掛起(我們記錄它 為 A。如果這個(gè)時(shí)候有新的消息到來(記錄為 B)作喘, MessageQueue#enqueueMessage() 會(huì)按照消息的執(zhí)行時(shí)間排序插入理疙,然后喚醒 MessageQueue#next() 處理消息。如果 B 不是定時(shí)消息立即處理泞坦,如果是定時(shí)消息更新掛起時(shí)間繼續(xù)阻塞窖贤,等到阻塞時(shí)間到的時(shí)候就會(huì)立即喚醒 next 方法處理。

為什么 MessageQueue#next() 需要死循環(huán)? loop 的死循環(huán)還不夠用嗎贰锁?

Loop#loop 里面的死循環(huán)是為了防止退出的赃梧,而 MessageQueue#next() 的死循環(huán)是為了確認(rèn)到底有沒有消息 參考:對(duì)于 MessageQueue 的解讀

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;
        }

        //為-1時(shí),說明是第一次循環(huán)豌熄,在當(dāng)前消息隊(duì)列中沒有MSG的情況下授嘀,需要處理注冊(cè)的Handler
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 超時(shí)時(shí)間。即等待xxx毫秒后锣险,該函數(shù)返回蹄皱。如果值為0,則無(wú)須等待立即返回芯肤。如果為-1巷折,則進(jìn)入無(wú)限等待,直到有事件發(fā)生為止
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {//???
                Binder.flushPendingCommands();
            }

            // 該函數(shù)提供阻塞操作崖咨。如果nextPollTimeoutMillis為0盔几,則該函數(shù)無(wú)須等待,立即返回掩幢。
            //如果為-1逊拍,則進(jìn)入無(wú)限等待,直到有事件發(fā)生為止际邻。
            //在第一次時(shí)芯丧,由于nextPollTimeoutMillis被初始化為0,所以該函數(shù)會(huì)立即返回
            //從消息鏈的頭部獲取消息
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();//記錄當(dāng)前時(shí)間
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {//message不為空世曾,但沒有執(zhí)行者
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {//尋找Asynchronous的消息
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        //判斷頭節(jié)點(diǎn)所代表的message執(zhí)行的時(shí)間是否小于當(dāng)前時(shí)間
                        //如果小于缨恒,讓loop()函數(shù)執(zhí)行message分發(fā)過程谴咸。否則,需要讓線程再次等待(when–now)毫秒
                        // 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;//將隊(duì)列設(shè)置為非 blocked 狀態(tài)
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
                        msg.markInUse(); //將消息設(shè)置為 inuse
                        return msg;
                    }
                } else {
                //如果頭節(jié)點(diǎn)為空骗露,消息鏈中無(wú)消息岭佳,設(shè)置nextPollTimeoutMillis為-1,讓線程阻塞住萧锉,
                //直到有消息投遞(調(diào)用enqueueMessage方法)珊随,并利用nativeWake方法解除阻塞
                    // 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)) {
                    //第一次進(jìn)入,當(dāng)前無(wú)消息柿隙,或還需要等待一段時(shí)間消息才能分發(fā)叶洞,獲得idle handler的數(shù)量
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    //如果沒有idle handler需要執(zhí)行,阻塞線程進(jìn)入下次循環(huán)
                    // 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("MessageQueue", "IdleHandler threw exception", t);
                }

                //如果keep=false禀崖,表明此idler只執(zhí)行一次衩辟,把它從列表中刪除。如果返回true波附,則表示下次空閑時(shí)艺晴,會(huì)再次執(zhí)行
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
        
            //pendingIdleHandlerCount=0,是為了避免第二次循環(huán)時(shí)掸屡,再一次通知listeners
            //如果想剩余的listeners再次被調(diào)用封寞,只有等到下一次調(diào)用next()函數(shù)了
            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // nextPollTimeoutMillis=0,是為了避免在循環(huán)執(zhí)行idler.queueIdle()時(shí)折晦,有消息投遞钥星。
            //所以nextPollTimeoutMillis=0后沾瓦,第二次循環(huán)在執(zhí)行nativePollOnce時(shí)满着,會(huì)立即返回
            //如果消息鏈中還是沒有消息,那么將會(huì)在continue;處執(zhí)行完第二次循環(huán)贯莺,進(jìn)行第三次循環(huán)风喇,然后進(jìn)入無(wú)限等待狀態(tài)
            // 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;
        }
    }

重點(diǎn):

  1. nativePollOnce(ptr, nextPollTimeoutMillis); nextPollTimeoutMillis: 0 不阻塞,-1 無(wú)限阻塞等待喚醒缕探。
  2. 死循環(huán)最多執(zhí)行三次:
    1. 第一次循環(huán)魂莫,如果消息鏈中有合適的消息,就拋出 message 去處理爹耗。如果沒有耙考,則會(huì)通知各 listeners 線程空閑了。執(zhí)行完后為了避免在 listners 執(zhí)行的過程中有消息投遞潭兽,那么此時(shí)重置 nextPollTimeoutMillis = 0倦始。
    2. 然后進(jìn)行第二次循環(huán),由于此時(shí) nextPollTimeoutMillis 為0山卦,則 nativePollOnce 不會(huì)阻塞鞋邑,立即返回,取出 message,如果此時(shí)消息鏈中還是沒有 message枚碗,則會(huì)在將會(huì)在 continue 處結(jié)束第二次循環(huán)逾一,此時(shí) nextPollTimeoutMillis 已被設(shè)置為-1,
    3. 第三次循環(huán)時(shí)肮雨,nativePollOnce 發(fā)現(xiàn) nextPollTimeoutMillis 為-1遵堵,則進(jìn)入無(wú)限等待狀態(tài),直到有新的message 被投遞到隊(duì)列中來酷含。當(dāng)有新的 message 后鄙早,由于 enqueueMessage 中調(diào)用了 nativeWake 函數(shù),nativePollOnce 會(huì)從等待中恢復(fù)回來并返回椅亚,繼續(xù)執(zhí)行限番,然后將新的 message 拋出處理,for 循環(huán)結(jié)束呀舔。

enqueueMessage

enqueueMessage 有排序功能弥虐,按照時(shí)間入隊(duì)。

 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("MessageQueue", e.getMessage(), e);
                msg.recycle();//把這個(gè)消息放回到消息池中
                //獲得msg時(shí)霜瘪,先去消息池中看看有沒有被回收的msg,如果有惧磺,就不用創(chuàng)建新的msg了
                return false;
            }

            msg.markInUse();
            msg.when = when;//從消息隊(duì)列中取出絕對(duì)時(shí)間戳
            Message p = mMessages;//指向隊(duì)首
            boolean needWake;
            //如果當(dāng)前的消息鏈為空颖对,或者要插入的MSG為QUIT消息,或者要插入的MSG時(shí)間小于消息鏈的第一個(gè)消息
            //在隊(duì)首插入
            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 {
                //否則磨隘,我們需要遍歷該消息鏈缤底,將該MSG插入到合適的位置
                // 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;
            }

//neekWake=mBlocked, 如果mBlocked為ture,表面當(dāng)前線程處于阻塞狀態(tài)番捂,即nativePollOnce處于阻塞狀態(tài)
//當(dāng)通過enqueueMessage插入消息后个唧,就要把狀態(tài)改為非阻塞狀態(tài),所以通過執(zhí)行nativeWake方法设预,觸發(fā)nativePollOnce函數(shù)結(jié)束等待
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

本人最近在整理一套專屬 Android 進(jìn)階筆記歡迎 start
Read More

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徙歼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鳖枕,更是在濱河造成了極大的恐慌魄梯,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宾符,死亡現(xiàn)場(chǎng)離奇詭異酿秸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吸奴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門允扇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缠局,“玉大人,你說我怎么就攤上這事考润∠猎埃” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵糊治,是天一觀的道長(zhǎng)唱矛。 經(jīng)常有香客問我,道長(zhǎng)井辜,這世上最難降的妖魔是什么绎谦? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮粥脚,結(jié)果婚禮上窃肠,老公的妹妹穿的比我還像新娘。我一直安慰自己刷允,他們只是感情好冤留,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著树灶,像睡著了一般纤怒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上天通,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天泊窘,我揣著相機(jī)與錄音,去河邊找鬼像寒。 笑死烘豹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萝映。 我是一名探鬼主播吴叶,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阐虚,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼序臂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起实束,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奥秆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后咸灿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體构订,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年避矢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悼瘾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囊榜。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亥宿,靈堂內(nèi)的尸體忽然破棺而出卸勺,到底是詐尸還是另有隱情,我是刑警寧澤烫扼,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布曙求,位于F島的核電站,受9級(jí)特大地震影響映企,放射性物質(zhì)發(fā)生泄漏悟狱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一堰氓、第九天 我趴在偏房一處隱蔽的房頂上張望挤渐。 院中可真熱鬧,春花似錦双絮、人聲如沸挣菲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)白胀。三九已至,卻和暖如春抚岗,著一層夾襖步出監(jiān)牢的瞬間或杠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工宣蔚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留向抢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓胚委,卻偏偏與公主長(zhǎng)得像挟鸠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子亩冬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355