帶著問題學習 Android Handler 消息機制

學習 Android Handler 消息機制

一、提出問題

面試時常被問到的問題:

  • 簡述 Android 消息機制
  • Android 中 Handler,Looper,MessageQueue,Message 有什么關系?

這倆問題其實是一個問題,其實只要搞清楚了 Handler,Looper,MessageQueue,Message 的作用和聯(lián)系,就理解了 Android 的 Handler 消息機制。那么再具體一點:

  1. 為什么在主線程可以直接使用 Handler?
  2. Looper 對象是如何綁定 MessageQueue 的?
  3. MessageQueue 里的消息從哪里來?Handler是如何往MessageQueue中插入消息的?
  4. Message 是如何綁定 Handler 的?
  5. Handler 如何綁定 MessageQueue?
  6. 關于 handler,在任何地方 new handler 都是什么線程下?
  7. Looper 循環(huán)拿到消息后怎么處理?

二、解決問題

那么,我們從主線程的消息機制開始分析:

2.1 主線程 Looper 的創(chuàng)建和循環(huán)

Android 應用程序的入口是 main 函數(shù),主線程 Looper 的創(chuàng)建也是在這里完成的。

ActivityThread --> main() 函數(shù)

public static void main(){
        // step1: 創(chuàng)建主線程Looper對象
        Looper.prepareMainLooper();
        
        ActivityThread thread = new ActivityThread();
        // 綁定應用進程,布爾標記是否為系統(tǒng)進程
        thread.attach(false);
        // 實例化主線程 Handler
        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        // step2: 開始循環(huán)
        Loop.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLooper()用來創(chuàng)建主線程的 Looper 對象,接下來先看這個方法的實現(xiàn)。

2.1.1 創(chuàng)建主線程 Looper

Looper --> prepareMainLooper()

private static Looper sMainLooper;  // guarded by Looper.class

public static void prepareMainLooper(){
        // step1: 調用本類 prepare 方法
        prepare(false);
        // 線程同步,如果變量 sMainLooper 不為空拋出主線程 Looper 已經創(chuàng)建
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // step2: 調用本類 myLooper 方法
            sMainLooper = myLooper();
        }
}

prepareMainLooper() 方法主要是使用 prepare(false) 創(chuàng)建當前線程的 Looper 對象,再使用 myLooper() 方法來獲取當前線程的 Looper 對象。

step1: Looper --> prepare()

// ThreadLocal 為每個線程保存單獨的變量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// Looper 類的 MessageQueue 變量
final MessageQueue mQueue;
// quitAllowed 是否允許退出,這里是主線程的 Looper 不可退出
private static void prepare(boolean quitAllowed) {
        // 首先判定 Looper 是否存在
        if(sThreadLocal.get() != null){
                throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 保存線程的副本變量
        sThreadLoacal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
  • prepare() 方法中用 ThreadLocal 來保存主線程的 Looper 對象。ThreadLocal 可以看作是一個用來儲存數(shù)據(jù)的類,類似 HashMap、ArrayList等集合類,它存放著屬于當前線程的變量。

  • ThreadLocal 提供了 get/set 方法分別用來獲取和保存變量。
    比如在主線程通過 prepare() 方法來創(chuàng)建 Looper 對象,并使用 sThreadLoacal.set(new Looper(quitAllowed)) 來保存主線程的 Looper 對象,那么在主線程調用 myLooper()(實際調用了 sThreadLocal.get() 方法) 就是通過 ThreadLocal 來獲取主線程的 Looper 對象。如果在子線程調用這些方法就是通過 ThreadLocal 保存和獲取屬于子線程的 Looper 對象。

更多關于 ThreadLocal 的原理:

深入剖析ThreadLocal實現(xiàn)原理以及內存泄漏問題

問題1:為什么在主線程可以直接使用 Handler?
因為主線程已經創(chuàng)建了 Looper 對象并開啟了消息循環(huán),通過上文的代碼就可以看出來。

問題2:Looper 對象是如何綁定 MessageQueue 的?或者說 Looper 對象創(chuàng)建 MessageQueue 過程。
很簡單,Looper 有個一成員變量 mQueue,它就是 Looper 對象默認保存的 MessageQueue。上面代碼中 Looper 有一個構造器,新建 Looper 對象時會直接創(chuàng)建 MessageQueue 并賦值給 mQueue。
問題2解決:在 new Looper 時就創(chuàng)建了 MessageQueue 對象并賦值給 Looper 的成員變量 mQueue。

step2: Looper --> myLooper()

// 也就是使用本類的ThreadLocal對象獲取之前創(chuàng)建保存的Looper對象
public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
}

這個方法就是通過 sThreadLocal 變量獲取當前線程的 Looper 對象,比較常用的一個方法。上文主線程 Looper 對象創(chuàng)建后使用該方法獲取了 Looper 對象。

2.1.2 開始循環(huán)處理消息

回到最開始的 main() 函數(shù),在創(chuàng)建了 Looper 對象以后就調用了 Looper.loop() 來循環(huán)處理消息,貼一下大致代碼:

public static void main(){
        // step1: 創(chuàng)建主線程Looper對象
        Looper.prepareMainLooper();
        ...
        // step2: 開始循環(huán)
        Loop.loop();
}

Looper --> loop()

public static void loop() {
    // step1: 獲取當前線程的 Looper 對象
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // step2: 獲取 Looper 保存的 MessageQueue 對象
    final MessageQueue queue = me.mQueue;

    ...
    // step3: 循環(huán)讀取消息,如果有則調用消息對象中儲存的 handler 進行發(fā)送
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        try {
            // step4: 使用 Message 對象保存的 handler 對象處理消息
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();
    }
}
  • step1 : myLooper() 方法就是通過 ThreadLocal 獲取當前線程的 Looper 對象,注意在哪個線程使用該方法就獲取的該線程的 Looper 對象。
  • step2 :me.mQueue,這個 mQueue 就是上面問題2所說的在 Looper 對象創(chuàng)建時新建的 MessageQueue 變量。
  • step3 :接下來是一個 for 循環(huán),首先通過 queue.next() 來提取下一條消息,具體是怎么提取的可以參考下面文章的 4.2 節(jié):

Android消息機制1-Handler(Java層)

獲取到下一條消息,如果 MessageQueue 中沒有消息,就會進行阻塞。那么如果存在消息,它又是怎么放入 MessageQueue 的呢?或者說MessageQueue 里的消息從哪里來?Handler是如何往MessageQueue中插入消息的?先不說這個,把這個問題叫作問題3后面分析。

  • step4 :msg.target.dispatchMessage(msg);這個方法最終會調用 Handler 的 handleMessage(msg) 方法。同時這里又產生個問題:msg.target 是何時被賦值的?,也就是說Message 是如何綁定 Handler 的?先稱之為問題4。那么接著看 Handler 的 dispatchMessage 方法:

Handler --> dispatchMessage

public void dispatchMessage(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();
}

public void handleMessage(Message msg) {
}

可以看到該方法最后執(zhí)行了 handleMessage() 方法,這是一個空方法也就是需要我們覆寫并實現(xiàn)的。另外 dispatchMessage() 也體現(xiàn)出一個問題:

消息分發(fā)的優(yōu)先級:

  • Message 的回調方法:message.callback.run(); 優(yōu)先級最高;
  • Handler 的回調方法:mCallback.handleMessage(msg)優(yōu)先級次于上方;
  • Handler 的回調方法:handleMessage() 優(yōu)先級最低。

到這里 Looper 循環(huán)并通過 Handler 發(fā)送消息有一個整體的流程了,接下來分析 Handler 在消息機制中的主要作用以及和 Looper、Message 的關系。

2.2 Handler 的創(chuàng)建和作用

上面說到 loop() 方法在不斷從消息隊列 MessageQueue 中取出消息(queue.next() 方法),如果沒有消息則阻塞,反之交給 Message 綁定的 Handler 處理。回顧一下沒解決的兩個問題:

  • 問題3:MessageQueue 里的消息從哪里來?Handler 是如何往 MessageQueue 中插入消息的?
  • 問題4:msg.target 是何時被賦值的?,也就是說Message 是如何綁定 Handler 的?

既然要解決 Handler 插入消息的問題,就要看 Handler 發(fā)送消息的過程。

2.2.1 Handler 發(fā)送消息

Handler --> sendMessage(Message msg);

final MessageQueue mQueue;

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
// 發(fā)送延時消息
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 指定時間發(fā)送消息
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);
}
// 處理消息,賦值 Message 對象的 target,消息隊列插入消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到調用 sendMessage(Message msg) 方法最終會調用到 enqueueMessage() 方法,這個方法主要有兩個作用:賦值 Message 對象的 target、消息隊列插入消息。

  • 賦值 msg 的 target:msg.target = this 把發(fā)送消息的 Handler 賦值給 msg 對象的 target。那么問題 4 就解決了:Handler 執(zhí)行發(fā)送消息的過程中將自己綁定給了 Message 的 target,這樣兩者之間就產生了聯(lián)系
  • 消息隊列插入消息:queue.enqueueMessage(msg, uptimeMillis) queue 是 MessageQueue 的一個實例,queue.enqueueMessage(msg, uptimeMillis)是執(zhí)行 MessageQueue 的enqueueMessage方法來插入消息。這樣問題 3 就找到答案:Handler 在發(fā)送消息的時候執(zhí)行 MessageQueue 的enqueueMessage方法來插入消息;關于 MessageQueue 是怎么執(zhí)行插入消息的過程,參考下方文章 4.3 節(jié)

Android消息機制1-Handler(Java層)

  • 上面 Handler 發(fā)送消息使用了 MessageQueue 的實例 queue,可以看到這個 queue 是上一個方法 sendMessageAtTime 中由 Handler 的成員變量 mQueue 賦值的,那么 mQueue 是哪來的?問題 5:Handler 如何綁定 MessageQueue?先劇透一下 Handler 綁定的是 Looper 的 MessageQueue 對象,Looper 的 MessageQueue 對象是在 Looper 創(chuàng)建時就 new 的。

要了解 Handler 的 MessageQueue 對象是怎么賦值的就要看 Handler 的構造函數(shù)了,Handler 創(chuàng)建的時候作了一些列操作比如獲取當前線程的 Looper,綁定 MessageQueue 對象等。

2.2.2 Handler 的創(chuàng)建

下面是 Handler 無參構造器和主要的構造器,另外幾個重載的構造器有些是通過傳遞不同參數(shù)調用包含兩個參數(shù)的構造器。兩個參數(shù)構造函數(shù)第一個參數(shù)為 callback 回調,第二個函數(shù)用來標記消息是否異步。

// 無參構造器
public Handler() {
     this(null, false);
}

public Handler(Callback callback, boolean async) {
    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());
        }
    }
    // step1:獲取當前線程 Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // step2:獲取 Looper 對象綁定的 MessageQueue 對象并賦值給 Handler 的 mQueue
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
  • step1:調用myLooper() 方法,該方法是使用 sThreadLocal 對象獲取當前線程的 Looper 對象,回顧一下:
public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
}

如果獲取的 Looper 對象為 null,說明沒有執(zhí)行 Looper.prepare() 為當前線程保存 Looper 變量,就會拋出 RuntimeException。這里又說明了Handler 必須在有 Looper 的線程中使用,報錯不說,沒有 Looper 就無法綁定 MessageQueue 對象也就無法進行更多有關消息的操作。

  • step2:mQueue = mLooper.mQueue 說明了 Handler 的 MessageQueue 對象是由當前線程 Looper 的 MessageQueue 對象賦值的。這里問題 5 解決:Handler 在創(chuàng)建時綁定了當前線程 Looper 的 MessageQueue 對象。
  • 由于 Handler 和 Looper 可以看作使用的是同一個 MessageQueue 對象,所以 Handler 和 Looper 可以共享消息隊列 MessageQueue。Handler 發(fā)送消息(用 mQueue 往消息對列插入消息),Looper 可以方便的循環(huán)使用 mQueue 查詢消息,如果查詢到消息,就可以用 Message 對象綁定的 Handler 對象 target 去處理消息,反之則阻塞。

既然說到了 Handler 的構造器,就想到一個問題:問題 6:關于 handler,在任何地方 new handler 都是什么線程下?這個問題要分是否傳遞 Looper 對象來看。

  1. 不傳遞 Looper 創(chuàng)建 Handler:Handler handler = new Handler();上文就是 Handler 無參創(chuàng)建的源碼,可以看到是通過 Looper.myLooper() 來獲取 Looper 對象,也就是說對于不傳遞 Looper 對象的情況下,在哪個線程創(chuàng)建 Handler 默認獲取的就是該線程的 Looper 對象,那么 Handler 的一系列操作都是在該線程進行的。
  2. 傳遞 Looper 對象創(chuàng)建 Handler:Handler handler = new Handler(looper);那么看看傳入 Looper 的構造函數(shù):
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
// 第一個參數(shù)是 looper 對象,第二個 callback 對象,第三個消息處理方式(是否異步)
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看出來傳遞 Looper 對象 Handler 就直接使用了。所以對于傳遞 Looper 對象創(chuàng)建 Handler 的情況下,傳遞的 Looper 是哪個線程的,Handler 綁定的就是該線程。

到這里 Looper 和 Handler 就有一個大概的流程了,接下來看一個簡單的子線程 Handler 使用例子:

new Thread() {
    @Override
    public void run() {
        // step1
        Looper.prepare();
         // step2
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if(msg.what == 1){
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"HandlerTest",Toast.LENGTH_SHORT).show();
                        }
                    });
                     // step5
                    Looper.myLooper().quit();
                }
            }
        };
         // step3
        handler.sendEmptyMessage(1);
         // step4
        Looper.loop();
    }
}.start();
  • step1: 調用 Looper.prepare(); 為當前線程創(chuàng)建 Looper 對象,同時也就創(chuàng)建了 MessageQueue,之后將該線程的 Looper 對象保存在 ThreadLocal 中。注意這里的一切操作都在子線程中,如果不調用 Looper.prepare() 就使用 Handler 會報錯。
  • step2: 創(chuàng)建 Handler 對象,覆寫 handleMessage 處理消息,等待該 Handler 發(fā)送的消息處理時會調用該方法。
  • step3: 使用 handler 發(fā)送消息,這里只是示例,畢竟自己給自己發(fā)送消息沒啥必要。發(fā)送的過程中會將自己賦值給 msg.target,然后再將消息插入到 Looper 綁定的 MessageQueue 對象中。
  • step4: 調用 Looper.loop(); 首先獲取當前線程的 Looper 對象,根據(jù) Looper 對象就可以拿到 Looper 保存的 MessageQueue 對象 mQueue。有了 MessageQueue 對象就可以 for 循環(huán)獲取它保存的消息 Message 對象,如果消息不存在就返回 null 阻塞,反之則使用 Message 中保存的 Handler:msg.target 來處理消息,最終調用 handleMessage 也就是之前覆寫的方法來處理消息。
  • step5: 邏輯處理完畢以后,應在最后使用 quit 方法來終止消息循環(huán),否則這個子線程就會一直處于等待的狀態(tài),而如果退出Looper以后,這個線程就會立刻終止,因此建議不需要的時候終止Looper。

三、總結和其它

3.1 Handler、Looper、MessageQueue、Message

  1. Handler 用來發(fā)送消息,創(chuàng)建時先獲取默認或傳遞來的 Looper 對象,并持有 Looper 對象包含的 MessageQueue,發(fā)送消息時使用該 MessageQueue 對象來插入消息并把自己封裝到具體的 Message 中;
  2. Looper 用來為某個線程作消息循環(huán)。Looper 持有一個 MessageQueue 對象 mQueue,這樣就可以通過循環(huán)來獲取 MessageQueue 所維護的 Message。如果獲取的 MessageQueue 沒有消息時,便阻塞在 loop 的queue.next() 中的 nativePollOnce() 方法里,反之則喚醒主線程繼續(xù)工作,之后便使用 Message 封裝的 handler 對象進行處理。
  3. MessageQueue 是一個消息隊列,它不直接添加消息,而是通過與 Looper 關聯(lián)的 Handler 對象來添加消息。
  4. Message 包含了要傳遞的數(shù)據(jù)和信息。

3.2 Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死?

這是知乎上的問題,感覺問的挺有意思。平時可能不太會太深究這些問題,正好有大神回答那就記錄一下吧。

  1. 為什么不會因為死循環(huán)卡死?
    線程可以看作是一段可執(zhí)行代碼,當代碼執(zhí)行完畢線程的生命周期就該終止了。對于主線程來說我們不希望它執(zhí)行一段時間后退出,所以簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會被退出。既然是死循環(huán)那么怎么去處理消息呢,通過創(chuàng)建新線程的方式。
  2. 為這個死循環(huán)準備了一個新線程
    在進入死循環(huán)之前便創(chuàng)建了新binder線程,在代碼ActivityThread.main()中:
public static void main(){
        ...
        Looper.prepareMainLooper();

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

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

        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        // step2: 開始循環(huán)
        Loop.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
}

thread.attach(false);便會創(chuàng)建一個Binder線程(具體是指ApplicationThread,Binder的服務端,用于接收系統(tǒng)服務AMS發(fā)送來的事件),該Binder線程通過Handler將Message發(fā)送給主線程。

  1. 主線程的死循環(huán)一直運行是不是特別消耗CPU資源呢?

其實不然,這里就涉及到Linux pipe/epoll機制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法里,詳情見Android消息機制1-Handler(Java層),此時主線程會釋放CPU資源進入休眠狀態(tài),直到下個消息到達或者有事務發(fā)生,通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作。這里采用的epoll機制,是一種IO多路復用機制,可以同時監(jiān)控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。所以說,主線程大多數(shù)時候都是處于休眠狀態(tài),并不會消耗大量CPU資。

  1. Activity的生命周期是怎么實現(xiàn)在死循環(huán)體外能夠執(zhí)行起來的?
    上文 main 函數(shù)有一部分獲取 sMainThreadHandler 的代碼:
final H mH = new H();

public static void main(){
        ...
        if(sMainThreadHandler == null){
           sMainThreadHandler = thread.getHandler();
        }
        ...
}

final Handler getHandler() {
    return mH;
}

類 H 繼承了 Handler,在主線程創(chuàng)建時就創(chuàng)建了這個 Handler 用于處理 Binder 線程發(fā)送來的消息。

Activity的生命周期都是依靠主線程的Looper.loop,當收到不同Message時則采用相應措施:

在H.handleMessage(msg)方法中,根據(jù)接收到不同的msg,執(zhí)行相應的生命周期。
比如收到msg=H.LAUNCH_ACTIVITY,則調用ActivityThread.handleLaunchActivity()方法,最終會通過反射機制,創(chuàng)建Activity實例,然后再執(zhí)行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,則調用ActivityThread.handlePauseActivity()方法,最終會執(zhí)行Activity.onPause()等方法。 上述過程,我只挑核心邏輯講,真正該過程遠比這復雜。

3.3 Handler 使用造成內存泄露

  1. 有延時消息,要在Activity銷毀的時候移除Messages
  2. 匿名內部類導致的泄露改為匿名靜態(tài)內部類,并且對上下文或者Activity使用弱引用。

具體操作可以參考文章:

Handler內存泄露原理及解決方法

參考資料:

《Android 開發(fā)藝術探索》
Android消息機制1-Handler(Java層)
Android Handler消息機制實現(xiàn)原理

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

推薦閱讀更多精彩內容