Android 開發(fā)之Handler

談到Android開發(fā),就離不開線程操作,而我們需要在子線程中更新UI俐东,一般有以下幾種方式:

  • 1、view.post(Runnable action)
  • 2订晌、activity.runOnUiThread(Runnable action)
  • 3虏辫、AsyncTask
  • 4、Handler

而我們今天的主要目標就是Handler锈拨,首先我們看下handler的官方定義:

  • Handler允許你通過使用一個與線程的MessageQueue相關聯(lián)的Message和Runnable對象去發(fā)送和處理消息砌庄。 每個處理程序實例與單個線程和該線程的消息隊列相關聯(lián)。 當您創(chuàng)建一個新的處理程序時,它綁定到正在創(chuàng)建它的線程的線程/消息隊列 - 從那時起娄昆,它將向消息隊列傳遞消息和可運行文件佩微,并在消息發(fā)出時執(zhí)行它們 隊列。

  • Handler有兩個主要用途:(1)在可預見的時間內去調度消息和作為一些點的可運行程序(2)將不同于自己的線程執(zhí)行的操作排入隊列中稿黄。

  • 消息的調度是通過post(Runnable)喊衫,postAtTime(Runnable,long)杆怕,postDelayed(Runnable,long)壳贪,sendEmptyMessage(int)陵珍,sendMessage(Message),sendMessageAtTime(Message违施,long)和sendMessageDelayed(Message互纯,long)來完成的 。 后臺版本允許你將接收到的消息隊列調用的Runnable對象排入隊列; sendMessage版本允許你將包含將由處理程序的handleMessage(Message)方法處理的數(shù)據(jù)包(要求您實現(xiàn)Handler的子類)的Message對象排入隊列磕蒲。

  • 當發(fā)布或發(fā)送到Handler時留潦,你可以在消息隊列準備就緒后立即處理該項目或者指定一個延遲時間去處理該消息隊列,或者指定一個具體時間處理該消息辣往。 后兩者允許您實現(xiàn)超時兔院,定時和其他基于時間的行為。
  • 當為你的應用創(chuàng)建一個進程時站削,其主線程專用于運行一個消息隊列坊萝,該消息隊列負責管理頂級應用程序對象(activitys, broadcast receivers 等)及其創(chuàng)建的任何窗口许起。 你可以創(chuàng)建你自己的線程并通過Handler與主應用程序線程進行通信十偶。 這可以通過從你的新線程中調用同樣的post或sendMessage方法來實現(xiàn)。 給定的Runnable或Message將在Handler的消息隊列中進行調度园细,并在適當時進行處理惦积。

    在查看Handler源碼之前,我們先了解幾個類:
    Handler 猛频、Looper狮崩、MessageQueue、Message伦乔、ThreadLocation
    Handler我們就不在介紹該類厉亏,上面的官方文檔已給出了詳細的介紹,我們來看下其余幾個:

  • 1烈和、ThreadLocal:每個使用該變量的線程提供獨立的變量副本爱只,每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本招刹。ThreadLocal內部是通過map進行實現(xiàn)的恬试;
  • 2窝趣、Looper:可以理解為循環(huán)器,就是扶著管理一個消息循環(huán)隊列(MessageQueue)的;
  • 3训柴、MessageQueue:消息隊列哑舒,用來存放handler發(fā)布的消息
  • 4、Message:消息體幻馁,封裝了我們傳輸消息所需的數(shù)據(jù)結構洗鸵。

那么我們從哪里開始看起呢,好吧仗嗦, 從創(chuàng)建一個Handler實例為入口膘滨,首先我們看handler的構造方法:

```
public Handler() {
  this(null, false);
}
public Handler(Callback callback) {
  this(callback, false);
}
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
public Handler(boolean async) {
  this(null, async);
}
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());
      }
  }

  mLooper = Looper.myLooper();
  if (mLooper == null) {
      throw new RuntimeException(
          "Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}

public Handler(Looper looper, Callback callback, boolean async) {
  mLooper = looper;
  mQueue = looper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}

```
  • 可以看到,有多個構造方法稀拐,但是最終都會調用到最后一個火邓。我們看倒數(shù)第二個,有這么一句: mLooper = Looper.myLooper();這里我們看到了個looper德撬,可以看到铲咨,在handler里面會有一個mlooper對象與之關聯(lián),我們先不看mlooper是怎么來的蜓洪,我們先把下面的看完纤勒;繼續(xù)看下面一句: mQueue = mLooper.mQueue;我們的handler里面也有一個隊列的對象,實際上mQueue就是MessageQueue蝠咆,后面我們會講解到踊东。好的,繼續(xù)往下看刚操, mCallback = callback;一般情況下mCallback是null闸翅,我們通常new 一個Handler是不是調用的無參構造方法?callback的作用后面也會講解到菊霜,好的最后一句: mAsynchronous = async;表示我們的執(zhí)行過程是異步的還是同步的坚冀,一般情況下,默認是異步的鉴逞。
小結: Handler會存有Looper對象以及消息隊列mQueue记某,通過關聯(lián)looper與mQueue,可以想象构捡,handler要把message插入消息隊列中液南,最直接的方式當然是拿到消息隊列的實例,實現(xiàn)消息的發(fā)送勾徽;

  • 看了Handler的構造滑凉,接下來我們看下Looper.mLooper:
    public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
    }
    -可以看到,Looper.myLooper內部是調用了sThreadlocal.get();這個sThreadLocal其實就是我們之前說的ThreadLocal類的實例,他負責存儲當先線程的Looper實例畅姊;是不是真的呢咒钟?我們看下sThreadLocal在哪里賦值的,很好若未,我們找到了一個prepare方法朱嘴,看名字是準備的意思,也就是為我們準備我們需要的looper對象粗合,我們繼續(xù)看:

     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");
      }
      sThreadLocal.set(new Looper(quitAllowed));
      }
    
    
  • 首先我們可以看到萍嬉,會先判斷sThreadLocal.get() != null,說明Looper.prepare()只能被調用一次哦隙疚,不然就會拋出異常帚湘,這樣做是為了保證一個線程只有一個looper存在,然后我們的可以看到里面通過new Looper(quitAllowed)獲得當先線程的looper甚淡,我們繼續(xù)看Looper的構造方法:

      private Looper(boolean quitAllowed) {
      mQueue = new MessageQueue(quitAllowed);
      mThread = Thread.currentThread();
        }
    
    
  • 在looper的構造方法里,主要做了兩件事:1捅厂、創(chuàng)建一個looper管理的消息隊列 messageQueue贯卦;2、獲得當前的線程焙贷;

小結:Looper里面會存儲當前的線程撵割,以及所管理的消息隊列mQueue,一個Looper只會管理一個消息隊列MessageQueue辙芍;

  • 從上面的代碼中我們可以知道啡彬,在new 一個handler的同時,我們就獲得了一個handler實例故硅、一個當前線程的looper庶灿、一個looper管理的messagequeue,好像擁有了這三個對象吃衅,我們就可以發(fā)送消息了哦往踢。

  • 大家都知道looper從創(chuàng)建之后,就會開始循環(huán)徘层,在looper類的頂部峻呕,官方給出了一段代碼:

    class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
      Looper.prepare();
        mHandler = new Handler() {
           public void handleMessage(Message msg) {
              // process incoming messages here
           }
       };
        Looper.loop();
    }
    
    
  • 當我們使用handler發(fā)消息時,步驟是:

  • 1趣效、 調用 Looper.prepare(); 初始化所需的looper以及messageQueue
  • 2瘦癌、 實例化一個handler對象,我們可以在handleMessage獲得message做一些操作跷敬,此時handleMessage方法是在當前的Looper中執(zhí)行的讯私,也就是說,如果當前的looper是UI Looper,那么你可以更新UI妄帘,如果當前l(fā)ooper不是UI Looper楞黄,那么你更新UI肯定會報錯,你可能會說抡驼,我用handler時鬼廓,好像都不用調用Looper.prepare();,我怎么知道我當前的looper是UI的還是不是呢致盟,其實系統(tǒng)一般默認都幫我們獲取了UI 的Looper碎税,后面我們會講解到;
  • 3馏锡、調用 Looper.loop();讓Looper跑起來吧雷蹂!

  • Looper.prepare();我們前面已經(jīng)分析過了,主要是實例化一個messageQueue杯道,而且只能調用一次匪煌;那么我們重點就轉移懂到 Looper.loop();看源碼:

    /**
     * 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();
      if (me == null) {
          throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
      }
      final MessageQueue queue = me.mQueue;
    
      // Make sure the identity of this thread is that of the local process,
      // and keep track of what that identity token actually is.
      Binder.clearCallingIdentity();
      final long ident = Binder.clearCallingIdentity();
    
      for (;;) {
          Message msg = queue.next(); // might block
          if (msg == null) {
              // No message indicates that the message queue is quitting.
              return;
          }
    
          // This must be in a local variable, in case a UI event sets the logger
          final Printer logging = me.mLogging;
          if (logging != null) {
              logging.println(">>>>> Dispatching to " + msg.target + " " +
                      msg.callback + ": " + msg.what);
          }
    
          final long traceTag = me.mTraceTag;
          if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
              Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
          }
          try {
              msg.target.dispatchMessage(msg);
          } finally {
              if (traceTag != 0) {
                  Trace.traceEnd(traceTag);
              }
          }
    
          if (logging != null) {
              logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
          }
    
          // Make sure that during the course of dispatching the
          // identity of the thread wasn't corrupted.
          final long newIdent = Binder.clearCallingIdentity();
          if (ident != newIdent) {
              Log.wtf(TAG, "Thread identity changed from 0x"
                      + Long.toHexString(ident) + " to 0x"
                      + Long.toHexString(newIdent) + " while dispatching to "
                      + msg.target.getClass().getName() + " "
                      + msg.callback + " what=" + msg.what);
          }
    
          msg.recycleUnchecked();
      }
    }
    
    
  • 1、調用final Looper me = myLooper();獲得一個looper党巾,myLooper方法我們前面分析過萎庭,返回的是sThreadLocal中存儲的Looper實例,當me==null拋出異常齿拂;所以驳规,在looper執(zhí)行l(wèi)oop跑起來之前,我們要記得調用prepare()哦署海。當獲得當前的looper后吗购,調用 final MessageQueue queue = me.mQueue; 獲取looper管理的MessageQueue;然后我們可以看到一個很有意思的for語句: for (;;) {...} 這就是循環(huán)的開始了砸狞,此時我在想捻勉,我的天,這不是個無限死循話么趾代?怎么可能呢贯底?當然有退出的條件,不然不就傻逼了么撒强!

  • 2禽捆、我們可以看到:他會從looper的queue中獲取message,當message==null飘哨,循環(huán)停止胚想!
    Message msg = queue.next(); // might block
    if (msg == null) {
    // No message indicates that the message queue is quitting.
    return;
    }

  • 3、循環(huán)起來了芽隆,咱的looper也沒閑著浊服,他一直知道它的工作是什么统屈,我們可以看到:msg.target.dispatchMessage(msg);通過調用msg對象里的target對象的dispatchMessage(msg)方法把消息處理了。其實msg對象里的target對象就是我們new出來的handler牙躺,我們后面會講到愁憔。

小結:

looper主要做了如下工作:

  • 1、將自己與當前線程關聯(lián)在一起孽拷,通過ThreadLocal存儲當前線程的looper吨掌,確保當前線程只有一個looper實例;
  • 2脓恕、創(chuàng)建一個MessageQueue與當前l(fā)ooper綁定膜宋,通過prepare方法控制looper只能有一個messageQueue實例;
  • 3炼幔、調用loop()方法秋茫,不斷從MessageQueue中去取消息,通過調用msg.target.dispatchMessage(msg)處理;

  • 分析完了looper乃秀、接下來當然是hanlder發(fā)送消息了肛著,我們又回到了handler中,我們通過handler發(fā)消息跺讯,自然少不了我們得sendMessag方法策泣,那么我們就從它入手吧:

     public final boolean sendMessage(Message msg)
    {
      return sendMessageDelayed(msg, 0);
      }
    
     public final boolean sendEmptyMessage(int what)
    {
      return sendEmptyMessageDelayed(what, 0);
    }
    
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
      Message msg = Message.obtain();
      msg.what = what;
      return sendMessageDelayed(msg, delayMillis);
    }
    
    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
      Message msg = Message.obtain();
      msg.what = what;
      return sendMessageAtTime(msg, uptimeMillis);
    }
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
      if (delayMillis < 0) {
          delayMillis = 0;
      }
      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    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);
    }
    
    public final boolean sendMessageAtFrontOfQueue(Message msg) {
      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, 0);
    }
    
    

  • 可以看到我們的sendMessage有多種方法,但最終都會調用enqueueMessage方法抬吟,我們看enqueueMessage方法源碼:
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
    msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 可以看到里面會講當前的this賦值給msg.target,this
    當前就是我們當前的handler了统抬,這也就是之前在分析looper時說的火本,通過調用msg.target. dispatchMessage(msg)方法處理消息;后面后調用queue.enqueueMessage(msg, uptimeMillis);把消息放入當前的looper的MessageQueue隊列中去處理聪建,消息的發(fā)送流程就分析完了钙畔,發(fā)送了,接下來就是處理消息了金麸!

  • 我們用handler時擎析,都是在handleMessage方法中處理消息的,那么我們就從handleMessage方法入手:
    /**

    • Subclasses must implement this to receive messages.
      */
      public void handleMessage(Message msg) {
      }
  • 可以看到handleMessage是一個空的方法挥下,我們看handleMessage在哪被調用的呢揍魂?

      /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
      if (msg.callback != null) {
          handleCallback(msg);
      } else {
          if (mCallback != null) {
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
          handleMessage(msg);
      }
    }
    
    
  • 可以看到handleMessage在dispatchMessage中被調用了,奇怪棚瘟,怎么有兩個handleMessage方法呢现斋?大家不要弄混了哦,我們handler的handleMessage方法返回值時void偎蘸,所以mCallback.handleMessage肯定不是我們handler的了庄蹋;

  • 第一個縣判斷msg.callback!=null 調用 handleCallback(msg);
    然后我們追進去看:
    private static void handleCallback(Message message) {
    message.callback.run();
    }
    看到了run(),是不是想到了Runnable瞬内?其實message中的callback就是Runnable,我們可以從Message的創(chuàng)建函數(shù)中看到:
    private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
    }

  • 我們繼續(xù)回到dispatchMessage方法:也就是說限书,如果我們的給massge設置了callback虫蝶,那么我們的handleMessage方法就不會被執(zhí)行了,當然倦西,一般我們的massge.callback都是null的能真。后面就會繼續(xù)判斷mCallback!=null如果成立則調用mCallback.handleMessage(msg) mCallback其實是一個回調接口,可以看到调限,如果mCallback.handleMessage(msg)返回true舟陆,就不會執(zhí)行我們的Handler.handleMessage方法,所以我們其實可以通過給handler添加Callback來實現(xiàn)一個message的過濾或者攔截功能耻矮。

  • 我們的Handler.handleMessage經(jīng)過重重阻撓秦躯,最終終于可以執(zhí)行了。

總結:
  • 1裆装、在Looper.prepare()中會通過sThreadLocal保存一個looper實例踱承,控制當前線程只能有一個looper實例;
  • 2哨免、創(chuàng)建looper實例時茎活,會創(chuàng)建一個MessageQueue與looper關聯(lián);
  • 3琢唾、因為looper只會存在一個實例载荔,所以 當前線程也會只存在一個MessageQueue隊列;
  • 4采桃、調用Looper.loop()讓looper跑起來吧懒熙,然后looper就可以不停的從MessageQueue把消息拿出來,然后通過調用msg.target.dispatchMessage(msg)處理消息普办,也是讓消息最終進入我們的Handler.handleMessage方法工扎,被我們給處理了;所以我們在實例化handler時需要重寫handleMessage方法衔蹲;
  • 5肢娘、實例化Handler時,handler中會獲得當前線程的looper以及l(fā)ooper的messageQueue舆驶;
  • 6橱健、在調用sendMessage發(fā)送消息時,最終會調用enqueueMessage方法沙廉,在enqueueMessage方法里會將msg.target=handler畴博,講handler關聯(lián)到msg中,這樣looper在取出messageQueue中的消息時蓝仲,才知道該消息是要發(fā)給那個handler處理的俱病,將handler與msg關聯(lián)后辐怕,就將msg加入隊列中去了苇本,等待looper處理恢着。

  • 使用Handler注意事項:
  • 1桐汤、創(chuàng)建massage對象時,推薦使用obtain()方法獲取,因為Message內部會維護一個Message池用于Message的復用溢吻,這樣就可以避免 重新new message而沖內心分配內存维费,減少new 對象產生的資源的消耗。
  • 2促王、handler 的handleMessage方法內部如果有調用外部activity或者fragment的對象犀盟,一定要用弱飲用,handler最好定義成static的蝇狼,這樣可以避免內存泄漏阅畴;為什么呢?因為一但handler發(fā)送了消息迅耘。而handler內部有對外部變量的引用贱枣,此時handler已經(jīng)進入了looper的messageQueue里面。此時activity或者fragment退出了可視區(qū)域颤专,但是handler內部持有其引用且為強引用時纽哥,其就不會立即銷毀,產生延遲銷毀的情況栖秕。

面試問題:
  • Handler延遲消息處理:
    MessageQueue春塌,以隊列的形式管理message,message先進先出簇捍,但其內部是采用單鏈表來存儲消息列表摔笤。Handler的方法post(Runnable r)、postDelayed(Runnabler, long delayMillis)垦写、sendMessage(Message msg)、sendMessageDelayed(Message msg, long delayMillis)最終調用的都是sendMessageAtTime(Message msg, long uptimeMillis)彰触,其中又調用了enqueueMessage()將msg插入隊列中梯投。即不管發(fā)送的消息有沒有延遲,都會先插入隊列中况毅,如果有延遲的話分蓖,looper不會立刻取出消息,時間到后才會取出消息尔许,也就是延遲指延遲處理么鹤,不是延遲發(fā)送。
image

Handler可以調用sendMessageAtFrontOfQueue(Messagemsg)味廊,postAtFrontOfQueue(Runnable r)蒸甜,將消息插入隊頭棠耕,最先取出,最先執(zhí)行柠新,之后再處理隊列中的其他消息窍荧。

image

如果隊列中只有延遲消息,此時發(fā)送一個普通消息恨憎,普通消息會插入隊頭蕊退,最先處理,而不會等延遲消息取出后憔恳,再取出普通消息瓤荔。

  • 為什么在ActivityThread的main方法中有死循環(huán)(Loop.loop()),不會卡死

我們看到在ActivityThread的main中調用了 Looper.loop()

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

    //建立一個Binder通道(會創(chuàng)建新線程钥组,向主線程的messageQueue中發(fā)送消息)
    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    ......
    Looper.loop();

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

要說清楚問題输硝,我們要知道android是基于消息驅動的。具體體現(xiàn)在上述代碼中者铜,在代碼注釋的地方我們可以看到 ActivityThread thread = new ActivityThread(); thread.attach(false);這兩行代碼腔丧,執(zhí)行這兩句代碼會建立一個與ActivityManagerService連接的binder通道。ActivityManagerService負責管理所有activity的生命周期方法作烟,例如oncreat愉粤,onresume等,當ActivityManagerService開始需要activity執(zhí)行生命周期方法時拿撩,會首先通過建立好的binder通道調用應用程序進程的ApplicationThread的相關方法中衣厘。ApplicationThread會通過一個類型為Handler的H類將相關信息發(fā)送到主線程的消息隊列中,然后通過handler來處理這個消息压恒。這樣就不會導致程序的主線程卡死影暴。
上面只是說明了一種情況(activity的生命周期調用),其實所有的情況都是如此探赫。又比如界面的更新:當界面需要更新的時候型宙,也是講這個消息封裝在message對象中,然后添加到主線程的消息隊列中伦吠,由消息隊列統(tǒng)一管理妆兑。因此有消息時會進行處理,沒有消息時毛仪,主線程處于休眠狀態(tài)搁嗓。
所以,由于android主線程是基于消息驅動的箱靴,因此雖然有Loop.loop()這個死循環(huán)腺逛,但是主線程不會卡。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末衡怀,一起剝皮案震驚了整個濱河市棍矛,隨后出現(xiàn)的幾起案子安疗,更是在濱河造成了極大的恐慌,老刑警劉巖茄靠,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茂契,死亡現(xiàn)場離奇詭異,居然都是意外死亡慨绳,警方通過查閱死者的電腦和手機掉冶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脐雪,“玉大人厌小,你說我怎么就攤上這事≌角铮” “怎么了璧亚?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脂信。 經(jīng)常有香客問我癣蟋,道長,這世上最難降的妖魔是什么狰闪? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任疯搅,我火速辦了婚禮,結果婚禮上埋泵,老公的妹妹穿的比我還像新娘幔欧。我一直安慰自己,他們只是感情好丽声,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布礁蔗。 她就那樣靜靜地躺著,像睡著了一般雁社。 火紅的嫁衣襯著肌膚如雪浴井。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天霉撵,我揣著相機與錄音磺浙,去河邊找鬼。 笑死喊巍,一個胖子當著我的面吹牛,可吹牛的內容都是我干的箍鼓。 我是一名探鬼主播崭参,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼款咖!你這毒婦竟也來了何暮?” 一聲冷哼從身側響起奄喂,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎海洼,沒想到半個月后跨新,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡坏逢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年域帐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片是整。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡肖揣,死狀恐怖,靈堂內的尸體忽然破棺而出浮入,到底是詐尸還是另有隱情龙优,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布事秀,位于F島的核電站彤断,受9級特大地震影響,放射性物質發(fā)生泄漏易迹。R本人自食惡果不足惜宰衙,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赴蝇。 院中可真熱鬧菩浙,春花似錦、人聲如沸句伶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽考余。三九已至先嬉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間楚堤,已是汗流浹背疫蔓。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留身冬,地道東北人衅胀。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像酥筝,于是被迫代替她去往敵國和親滚躯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容