Handler消息處理機制分析

Handler經常用,然后自己總結一下下
圖片發自簡書App

一. What、Handler 是什么

Handler 與 Message、MessageQueue、Looper 一起構成了 Android 的消息機制,Android 系統通過大量的消息來與用戶進行交互,View 的繪制、點擊事件、Activity 的生命周期回調等都作為消息由主線程的 Handler 來處理。

Handler 在消息機制中的作用是:發送和處理消息。

Handler 還有另一個重要的作用,跨線程通信。最常見的就是子線程請求網絡,然后使用 Handler 將請求到的數據 post 到主線程刷新 UI,大名鼎鼎的 Retrofit 也是這么做的。

二. How、如何使用 Handler

創建 Handler

private Handler handler = new Handler() {
// 重寫 handleMessage 來根據不同 what 來處理 Message
// 這個方法在 Handler 創建的線程執行
@Override public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
MLog.i(msg.obj);
break;
case 1:
break;
default:

    }
}

};
創建并發送 Message

// 獲取一個 Message
Message message = Message.obtain();
message.what = 0;
message.obj = new Object();
// 使用 Handler 發送 Message
// 消息發送完成后 Handler 的 handleMessage(Message msg) 會處理消息
handler.sendMessage(message);

// 延遲 1s 發送 Message
handler.sendMessageDelayed(message, 1000);
// 發送一個空的 Message
handler.sendEmptyMessage(msg.what);
// 延遲發送一個空的 Message
handler.sendEmptyMessageDelayed(0, 1000);

// 還可以這樣
// 創建 Message 并綁定 Handler
Message message = handler.obtainMessage();
message.what = 0;
message.obj = new Object();

// 發送 Message
message.sendToTarget();
使用 Handler 子線程請求數據,主線程刷新 UI

// 1. 在主線程創建 Handler(略)
// 2. 子線程請求數據,主線程刷新 UI
new Thread(new Runnable() {
@Override public void run() {
// 獲取網絡數據
final List<Object> datas = getNetData();

    // 方法一:將數據作為 Message 的 obj 發送出去,在 handleMessage 中刷新 UI
    Message msg = Message.obtain();
    msg.what = 1;
    msg.obj = data;
    handler.sendMessage(msg);

    // 方法二:直接在 post 中刷新 UI
    handler.post(new Runnable() {
        @Override public void run() {
          // 使用 datas 刷新 UI
          // 這個方法也會在 Handler 創建的線程執行
        }
    });
}

}).start();
三. Handler 的內存泄漏

不得不說,上面使用 Handler 的方法會有內存泄漏的風險

Handler 內存泄漏的兩個原因

Java 中非靜態內部類和匿名內部類會持有外部類的引用

// 這是一個外部類 Handler 不會持有外部類引用
// 顯然 handleMessage 沒地方寫了
Handler handler = new Handler();

// 重寫 handleMessage 后將得到一個內部類 Handler,以內 handleMessage 是在外部類中實現的
// 它持有外部類引用,可能會引起內存泄漏
Handler handler = new Handler() {
@Override public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
MLog.i(msg.obj);
break;
case 1:
break;
default:

    }
}

};

// 這里 Handler 是一個匿名類,但不是內部類
// Runnable 是一個匿名內部類,持有外部類引用,可能會引起內存泄漏
new Handler().post(new Runnable() {
@Override public void run() {
// ...
}
});
?

Handler 的生命周期比外部類長。

分析

非靜態的內部 Handler 子類、匿名 Handler 子類會持有外部類的引用(Activity),而 Handler 可能會因為要等待處理耗時操作導致存活時間超過 Activity,或者消息隊列中存在未被 Looper 處理的 Message ,而 Message 會持有 Handler 的引用。于是,在 Activity 退出時,其引用還是被 Handler 持有,導致 Activity 無法被及時回收,造成內存泄露。
非靜態的內部 Runnable 子類、匿名 Runnable 子類 post 到任意 Handler 上時,Runnable 其實是 Massage中的 Callback,持有 Message 引用,如果這個 Massage 在消息隊列還沒有被處理,那么就會造成 Runnable 一直持有外部類的引用而造成內存泄露。
解決方案:

通過靜態內部類或者外部類來聲明 Handler 和 Runnable。
通過弱引用來拿到外部類的變量。
在 Activity/Fragment 銷毀的時候請空 MessageQueue 中的消息。
代碼

// Handler 弱引用封裝
public class SafetyHandler<T> extends Handler {
/**
* 外部引用, 例如 Activity, Fragment, Dialog, View 等
*/
private WeakReference<T> mTargetRef;
public SafetyHandler() {
}
public SafetyHandler(T target) {
this.mTargetRef = new WeakReference<>(target);
}

public T getTarget() {
    if (isTargetAlive()) {
        return mTargetRef.get();
    } else {
        removeCallbacksAndMessages(null);
        return null;
    }
}

public void setTarget(T target) {
    this.mTargetRef = new WeakReference<>(target);
}

private boolean isTargetAlive() {
    return mTargetRef != null && mTargetRef.get() != null;
}

}

// 在 Fragment 中使用方法
// 想重寫 handleMessage 的話,要創建靜態內部類或者外部類,否則有內存泄漏風險
private static class MyHandler extends SafetyHandler<MyFragment> {

MyHandler(MyFragment fragment) {
      super(fragment);
}

@Override public void handleMessage(Message msg) {
    super.handleMessage(msg);
    if(getTarget() != null) {
        MyFragment fragment = getTarget();
        switch (msg.what) {
           // 操作 fragment
        }
    }
}

}

// 聲明 Handler
MyHandler handler = new MyHandler(this);

// 使用 Handler
handler.sendMessage() ...

// onDestroy
@Override public void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
?

四. Why、Handler 消息機制的原理

這部分從 ActivityThread 的 main 方法出發,打通整個消息機制的流程,結合源碼體驗效果更佳。
概述

介紹消息機制的原理前,我們先來看一下 Handler 與 Message、MessageQueue、Looper 這個四個類的作用

Handler:前面已經說過,Handler 負責發送和處理 Message。
Message:消息,負責傳遞標示(what) 和數據(obj) ;每個 Message 都會通過 target 這個成員變量來綁定一個 Handler,由這個 Handler 來發送和處理 Message。
MessageQueue:消息隊列,負責存放有 Handler 發送過來的消息;每個 Handler 中都有一個 final MessageQueue mQueue,Handler 發送消息就是把消息加入這個 MessageQueue 。
Looper:負責不斷的從 MessageQueue 中取出消息然后交給 Handler(Message#target ) 處理;每個 Looper 中都有一個唯一的消息隊列(final MessageQueue mQueue),每個 Handler 中都有一個 final Looper mLooper,Handler 中的 MessageQueue 就是來自 Looper。
注意:每個線程只能有一個 Looper 和 一個 MessageQueue,可以有多個 Handler,每個 Handler 可以發送和處理多個 Message。

另外,提到消息機制就不得不說一下 Android 中的主線程(UI 線程)

Android 中的主線程通過 Looper.loop() 進入一個無線循環中,不斷的從一個 MessageQueue 取出消息,處理消息,我們每觸發一個事件,就會向這個 MessageQueue 中添加一個消息,Looper 取出這個消息,Handler 處理這個消息,正是 Looper.loop() 在驅動著 Android 應用運行下去 ,這也是為什么 Looper.loop 為什么不會阻塞住主線程的原因(當然前提是在 ActivityThread 的 main 函數 中調用)。

正式進入源碼分析

本源碼分析基于 API 25,以下源碼中刪除了一些無關的代碼
1、在主線程的入口,ActivityThread 的 main 方法

public static void main(String[] args) {
// 準備主線程的 Looer
Looper.prepareMainLooper();
// 創建 ActivityThread
ActivityThread thread = new ActivityThread();
thread.attach(false);
// 獲取主線程的 Handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

    // 對消息隊列進行無線輪詢,處理消息
    Looper.loop();
    // 一旦跳出循環,拋出異常(Android 不允許跳出主線程的 Looper.loop())
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

-> Looper.prepareMainLooper()

public static void prepareMainLooper() {
// 準備一個 Looper
prepare(false);
synchronized (Looper.class) {
// main Looper 只能初始化一次,再次初始化會拋出異常
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// 獲取 main Looper
sMainLooper = myLooper();
}
}
-> prepare(false)

// 準備一個 Looper,quitAllowed 是否允許 Looper 中的 MessageQueue 退出
// 默認 prepare() 允許退出,主線程這里不允許退出
private static void prepare(boolean quitAllowed) {
// 先看下 sThreadLocal 是什么
// static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// ThreadLocal:線程本地存儲區,每個線程都有本地存儲區域,這個區域是每個線程私有的,不同的線程不能之間不能彼此訪問
// 如果 sThreadLocal 中有數據,拋出異常,換句話說 prepare() 這個函數每個線程只能執行一次
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 創建 Looper 保存到該線程的 ThreadLocal 中
sThreadLocal.set(new Looper(quitAllowed));
}
-> new Looper(quitAllowed)

private Looper(boolean quitAllowed) {
// 在 Looper 創建的時候創建一個消息隊列
// quitAllowed:消息隊列是否可以退出,主線的消息隊列肯定不允許退出,所以上面是 prepare(false)
// quitAllowed 為 false 執行 MessageQueue#quit 退出消息隊列時會出現異常
mQueue = new MessageQueue(quitAllowed);
// 獲取 Looper 存在于哪個線程
mThread = Thread.currentThread();
}
-> sMainLooper = myLooper()

public static @Nullable Looper myLooper() {
// 從 sThreadLocal 中獲取當前線程的 Looper
// 如果當前線程沒有掉用 Looper.prepare 返回 null
return sThreadLocal.get();
}
-> sMainThreadHandler = thread.getHandler();

final Handler getHandler() {
// 返回 mH
return mH;
}

// mH 在成員變量的位置 new H()
final H mH = new H();

// H 繼承了 Handler 封裝了一系列關于 Acitivty、Service 以及其他 Android 相關的操作
private class H extends Handler
總結:在主線程的 main 方法中,會創建主線程的 Looper、MessageQueue,然后進入 Looper.loop() 循環中,不斷的取出消息,處理消息,以此來驅動 Android 應用的運行。

2、Handler 的創建,Handler 的所有構造方法都會跳轉到下面兩個之一

public Handler(Callback callback, boolean async) {
// Hanlder 是匿名類、內部類、本地類時,如果沒有聲明為 static 則會出現內存泄漏的警告
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());
}
}

// 獲取 Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
// 消息隊列,從 Looper 中獲取
mQueue = mLooper.mQueue;
// 處理消息的回調接口
mCallback = callback;
// 處理消息的方式是否為異步,默認同步
mAsynchronous = async;
}

public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
總結:在 Handler 的構造方法中,Handler 和 Looper、MessageQueue 綁定起來,如果當前線程沒有 Looper 拋出異常(這也是為什么直接在子線程創建 Handler 會出現異常)。

3、使用 Handler 發送消息

-> sendMessageAtTime(Message msg, long uptimeMillis)

// 除了 sendMessageAtFrontOfQueue,Handler 所有的 post、sendMessage 都會跳到這個方法
// Message msg: 要發送的消息
// long uptimeMillis: 發送消息的絕對時間,通過 SystemClock.uptimeMillis() 加上我們自己的延遲時間 delayMillis 計算而來
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
// 消息隊列為空(可能已經退出)返回 false 入隊失敗
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);
}
-> sendMessageAtFrontOfQueue(Message msg)

// 發送消息到 MessageQueeu 的隊頭
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;
}
// 通過設置 uptimeMillis 為 0,是消息加入到 MessageQueue 的隊頭
return enqueueMessage(queue, msg, 0);
}
-> enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

// 所有 Handler 的 post 、sendMessage 系列方法和 runOnUiThread 最終都會調用這個方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// msg.target 是一個 Handler,將 Message 和 Handler 綁定
// 也就是用哪個 Handler 發送消息,這個 Message 就和哪個 Handler 綁定
msg.target = this;
// 如果設置了消息處理方式為異步處理
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// MessageQueue 的方法,將消息入隊
return queue.enqueueMessage(msg, uptimeMillis);
}
-> MessageQueue#enqueueMessage(Message msg, long when)

boolean enqueueMessage(Message msg, long when) {
// Messgae 沒有綁定 Handler 拋出異常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// Messgae 正在使用 拋出異常
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}

 synchronized (this) {
     // 消息隊列正在退出,回收 Message
     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();  // 調用 Message#recycleUnchecked() 
         return false;
     }
     msg.markInUse();  // 標記 Message 正在使用
     msg.when = when;  // 設置 Message 的觸發時間
   
     // mMessages 記錄著 MessageQueue 的隊頭的消息 
     Message p = mMessages;  
     boolean needWake;
     // MessageQueue 沒有消息、Message 觸發時間為 0、Messgae 觸發時間比隊頭 Message 早
     // 總之這個 Message 在 MessageQueue 中需要最先被分發
     if (p == null || when == 0 || when < p.when) {
         // New head, wake up the event queue if blocked.
         msg.next = p;     // 將以前的隊頭 Message 鏈接在這個 Message 后面
         mMessages = msg;  // 將這個 Message 賦值給 mMessages
         needWake = mBlocked;  // 隊列是否阻塞
     } else {
         // 標記隊列是否阻塞
         needWake = mBlocked && p.target == null && msg.isAsynchronous();
         Message prev;
       
         // 按照時間順序將 Message 插入消息隊列
         for (;;) {
             prev = p;   // prev 記錄隊頭
             p = p.next; // p 記錄隊頭的后一個
             // 隊頭后面沒有消息或者其觸發事件比要插入的 Message 晚,跳出循環
             if (p == null || when < p.when) {
                 break;
             }
             if (needWake && p.isAsynchronous()) {
                 needWake = false;
             }
         }
         // 將 Message 插入隊列
         msg.next = p; 
         prev.next = msg;
     }

     // We can assume mPtr != 0 because mQuitting is false.
     if (needWake) {
         nativeWake(mPtr);
     }
 }
 return true;

}
總結:到現在為止,我們的 Handler 已經將 Message 發送到了 MessageQueue,Message 靜靜的等待被處理。

4、Looper.loop() 還記得這個方法在 ActivityThread 的 main 調用了嗎?正是它在不斷處理 MessageQueue 里面的消息。

public static void loop() {
// 獲取 Looper.Looper.prepare 準備好的 Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 獲取 Looper 中的消息隊列
final MessageQueue queue = me.mQueue;

 // 進入無線循環
 for (;;) {
     // 取出下一條消息
     Message msg = queue.next(); 
     
     // 沒有消息,退出 loop
     // 其實上面 queue.next() 也是一個無限循環,獲取到消息就返回,沒有消息就一直循環
     if (msg == null) {
         return;
     }

     try {
         // msg.target 實際上就是一個 Handler
         // 獲取到了消息,使用綁定的 Handler#dispatchMessage 分發消息
         msg.target.dispatchMessage(msg);
     } finally {
         
     }

     // 釋放消息,把 Message 的各個變量清空然后放進消息池中
     msg.recycleUnchecked();
 }

}
5、Handler#dispatchMessage(msg) 消息是如何處理的

public void dispatchMessage(Message msg) {
// 1
if (msg.callback != null) {
handleCallback(msg);
} else {
// 2
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 3. 看到這個方法沒有!就是我們創建 Handler 時重寫的 handleMessage
// OK 整個流程打通!
handleMessage(msg);
}
}
總結:流程雖然通了,但是處理 Message 的方法貌似有三種(我標記了序號),而且我們的 handleMessage 的優先級最低,其他方法會在什么情況下執行呢? 直接說結論了,調用 Handler 的 post 系列方法會走序號1的處理,創建 Handler 傳入 Callback 會走序號2 的處理。

Handler 機制總結:想使用 Handler 必須要有 Looper,創建 Looper 的時候會創建 MessageQueue,在 Handler 的構造的時候會綁定這個 Looper 和 MessageQueue,Handler 將 Message 發送到 MessageQueue 中,Looper.loop() 會不斷的從 MessageQueue 取出消息再交給這個 Handler 處理。

五. HandlerThread 的使用及源碼解讀

在子線程中能直接創建 Handler 嗎?

new Thread(new Runnable() {
@Override public void run() {
new Handler().post(new Runnable() {
@Override public void run() {
MLog.i("Handler in " + Thread.currentThread().getName());
}
});
}
}).start();
答案前面提到了是不能,執行上面的代碼會出現 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 這個異常,異常提示我們,不能再沒有調用 Looper.prepare() 的線程中創建 Handler。

簡單修改下代碼就可以了,給線程準備好 Looper

new Thread(new Runnable() {
@Override public void run() {
// 準備一個 Looper,Looper 創建時對應的 MessageQueue 也會被創建
Looper.prepare();
// 創建 Handler 并 post 一個 Message 到 MessageQueue
new Handler().post(new Runnable() {
@Override public void run() {
MLog.i("Handler in " + Thread.currentThread().getName());
}
});
// Looper 開始不斷的從 MessageQueue 取出消息并再次交給 Handler 執行
// 此時 Lopper 進入到一個無限循環中,后面的代碼都不會被執行
Looper.loop();
}
}).start();
上面的操作 Android 都幫我們封裝好了,正是 HandlerThread 這個類。
HandlerThread 的簡單使用

// 1. 創建 HandlerThread
handlerThread = new HandlerThread("myHandlerThread") {
// onLooperPrepared 這個方法子線程執行,由線程的 run 方法調用,可以在里面直接創建 Handler
@Override protected void onLooperPrepared() {
super.onLooperPrepared();
new Handler().post(new Runnable() {
@Override public void run() {
// 注意:Handler 在子線程創建,這個方法也會運行在子線程,不可以更新 UI
MLog.i("Handler in " + Thread.currentThread().getName());
}
});
}
};

// 2. 準備 HandlerThread 的 Looper 并調用 onLooperPrepared
handlerThread.start();

// 3. 退出
@Override public void onDestroy() {
super.onDestroy();
handlerThread.quit();
}

// 也可以這樣用
// 1. 創建 HandlerThread 并準備 Looper
handlerThread = new HandlerThread("myHandlerThread");
handlerThread.start();

// 2. 創建 Handler 并綁定 handlerThread 的 Looper
new Handler(handlerThread.getLooper()).post(new Runnable() {
@Override public void run() {
// 注意:Handler 綁定了子線程的 Looper,這個方法也會運行在子線程,不可以更新 UI
MLog.i("Handler in " + Thread.currentThread().getName());
}
});

// 3. 退出
@Override public void onDestroy() {
super.onDestroy();
handlerThread.quit();
}
HandlerThread 源碼解讀

HandlerThread 繼承了 Thread,本質是一個擁有 Looper 的線程,因此在 HandlerThread 我們可以直接使用 Handler。

構造方法

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

// 傳入線程的名稱和優先級
// 注意 priority 的值必須來自 android.os.Process 不能來自 java.lang.Thread
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
run 方法:創建子線程的 Looper

@Override
public void run() {
mTid = Process.myTid();
// 準備一個 Looper
Looper.prepare();
synchronized (this) {
// 獲取 Looper
mLooper = Looper.myLooper();
// Looper 獲取成功后,喚醒 getLooper 的 wait
notifyAll();
}
Process.setThreadPriority(mPriority);
// Looper 準備好的回調,在這個方法里可以使用 Handler 了
onLooperPrepared();
// Looper 開始循環取消息
Looper.loop();
mTid = -1;
}
getLooper 方法:獲取子線程的 Looper

public Looper getLooper() {
// 線程沒有開始或者死亡,返回 null
if (!isAlive()) {
return null;
}

// If the thread has been started, wait until the looper has been created.
// Looper 的創建時在子線程完成的,而 getLooper 可能會在主線程調用
// 當 Looper 沒有創建完成時,使用 wait 阻塞等待
// 上面在 Looper 創建好后會 notifyAll 來喚醒 wait
 synchronized(this) {
    while (isAlive() && mLooper == null) {
        try {
              wait();
        } catch (InterruptedException e) {
        }
    }
}
return mLooper;

}
quit 和 quitSafely :結束 Looper 的運行

// quit
quit() -> looper.quit() -> mQueue.quit(false);
// quitSafely
quitSafely() -> looper.quitSafely() -> mQueue.quit(true);

// 這兩個方法最終都會調用到 MessageQueue 的 void quit(boolean safe) 方法
// 前者會直接移除 MessageQueue 中的所有消息,然后終止 MessageQueue
// 后者會將 MessageQueue 中已有消息處理完成后(不再接收新消息)終止 MessageQueue
六.參考文章

Android API 25 源碼
Android消息機制1-Handler(Java層)

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

推薦閱讀更多精彩內容