Android開發(fā)之異步任務(wù)消息機(jī)制

前言

文章是一篇學(xué)習(xí)筆記溯捆,主要記錄了閱讀Handler丑搔、MessageMessageQueueLooper源碼(Android 8.1)的一些心得體會(huì)啤月,但并沒有涉及到更深層次源碼的分析煮仇,比如MessageQueuenative層的源碼。而AsyncTask只是簡(jiǎn)單介紹其API谎仲。

概述

Android的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制欺抗,Handler的運(yùn)行需要底層的MessageQueueLooper的支撐,常用于更新UI强重。

MessageQueue用于存儲(chǔ)消息(Message)绞呈,內(nèi)部的存儲(chǔ)結(jié)構(gòu)采用了鏈表,而不是隊(duì)列间景,它提供插入和刪除消息的功能佃声,但不會(huì)處理消息。Looper,MessageQueue的管家倘要,它的loop方法會(huì)一直查看MessageQueue是否存在消息(Message)需要處理圾亏,如果有,就交給Handler來處理封拧。Handler是消息(Message)的發(fā)送者和處理者志鹃。

ThreadLocal并不是線程,它的作用是可以在每個(gè)線程中存儲(chǔ)數(shù)據(jù)泽西,這些數(shù)據(jù)對(duì)于其他線程是不可見的曹铃。每個(gè)線程中只會(huì)有一個(gè)Looper,可以通過ThreadLocal獲取到捧杉。

另外陕见,線程默認(rèn)是沒有Looper的,如果需要使用Handler就必須為線程創(chuàng)建味抖,比如在子線程评甜。而主線程已經(jīng)為我們創(chuàng)建好了,可以查閱ActivityThreadmain方法仔涩,其中包括了MessageQueue的初始化忍坷。

它們的數(shù)量級(jí)關(guān)系是:Handler(N):Looper(1):MessageQueue(1):Thread(1)

Handler的主要作用是將一個(gè)任務(wù)切換到某個(gè)指定的線程中執(zhí)行熔脂,這樣設(shè)計(jì)主要是為了解決Android只能再主線程中更新UI佩研。

系統(tǒng)為什么不允許再子線程中訪問UI呢?這是因?yàn)?code>Android的UI控件不是線程安全的锤悄,如果再多線程中并發(fā)訪問可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)韧骗。

加鎖的缺點(diǎn)有兩個(gè):首先加上鎖機(jī)制會(huì)讓UI訪問的邏輯變得復(fù)雜嘉抒;其次零聚,鎖機(jī)制會(huì)降低UI訪問的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。

創(chuàng)建方式

在此只列出一種創(chuàng)建Handler的方式隶症,其他的創(chuàng)建方式可以自行百度政模,但是,一定要注意:如果在沒有Looper的線程里創(chuàng)建Handler會(huì)報(bào)錯(cuò)的

    // 在主線程創(chuàng)建
    public class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            // 處理的代碼
        }
    }

    MyHandler handler = new MyHandler();

    // 子線程中執(zhí)行
    Message message = handler.obtainMessage();
    message.what = 1;
    message.obj = 123;
    handler.sendMessage(message);

原理圖

Handler原理圖(一).png
handler原理圖(二).png

源碼分析

先從ActivityThreadmain方法看起蚂会,里面有兩句需要注意的:

public static void main(String[] args) {
      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

      // CloseGuard defaults to true and can be quite spammy.  We
      // disable it here, but selectively enable it later (via
      // StrictMode) on debug builds, but using DropBox, not logs.
      CloseGuard.setEnabled(false);

      Environment.initForCurrentUser();

      // Set the reporter for event logging in libcore
      EventLogger.setReporter(new EventLoggingReporter());

      // Make sure TrustedCertificateStore looks in the right place for CA certificates
      final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
      TrustedCertificateStore.setDefaultUserDirectory(configDir);

      Process.setArgV0("<pre-initialized>");

      // 注意
      Looper.prepareMainLooper();

      ActivityThread thread = new ActivityThread();
      thread.attach(false);

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

      if (false) {
          Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
      }

      // End of event ActivityThreadMain.
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      // 注意
      Looper.loop();

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

大致就是說淋样,主線程的Looper已經(jīng)準(zhǔn)備好了。通常胁住,我們創(chuàng)建Handler會(huì)選擇繼承Handler并重寫handleMessage趁猴,因?yàn)楦割惖?code>handleMessage什么也不做。這里彪见,我們關(guān)注其構(gòu)造方法(無參的):

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

    // 注意以下幾句
    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;
}

假如我們?cè)僮泳€程中創(chuàng)建Handler儡司,但該線程并沒有Looper,它會(huì)拋出異常:

Can't create handler inside thread that has not called Looper.prepare()

可見余指,Handler的創(chuàng)建需要Looper捕犬。那這個(gè)異常如何解決呢?舉例如下:

new Thread("new Thread") {
        @Override
        public void run() {
            Looper.prepare();
            Handler handler = new Handler();
            Looper.loop();
        }
    }.start();

現(xiàn)在酵镜,我們回想一下碉碉,為什么在主線程可以直接創(chuàng)建Handler?正如前面所講述的淮韭,當(dāng)我們的App啟動(dòng)后垢粮,會(huì)調(diào)用ActivityThreadmain的方法,而LooperprepareMainLooper方法會(huì)被調(diào)用靠粪,也就是創(chuàng)建了Looper足丢,也就是滿足了Handler的創(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) {
        // 一個(gè)線程只能創(chuàng)建一個(gè)Looper
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
 }

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
  * to call this function yourself.  See also: {@link #prepare()}
  */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

不管是prepareMainLooper()方法還是prepare()方法庇配,最后都是通過prepare(boolean quitAllowed)來創(chuàng)建Looper斩跌。我們先來看sThreadLocal

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

詳細(xì)的介紹可以參考這篇博客:Android的消息機(jī)制之ThreadLocal的工作原理。它的作用是保存當(dāng)前線程的Looper捞慌,且線程間互不干擾耀鸦。

再看Looper的構(gòu)造方法,它完成了MessageQueue的創(chuàng)建:

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

總的來說啸澡,Android的主線程就是ActivityThread袖订,主線程的入口方法為main,再main方法中系統(tǒng)會(huì)通過Looper.prepareMainLooper()來創(chuàng)建主線程的Looper以及MessageQueue嗅虏,并通過Looper.loop()來開啟主線程的消息循環(huán)洛姑。

Message:消息載體

  • public int what:標(biāo)識(shí)
  • public int arg1:保存int數(shù)據(jù)
  • public int arg2:保存int數(shù)據(jù)
  • public Object obj:保存任意數(shù)據(jù)
  • long when:記錄應(yīng)該被處理的時(shí)間值,換句話說就是延時(shí)時(shí)間
  • Handler target:保存在主線程創(chuàng)建的Handler對(duì)象引用
  • Runnable callback:用來處理消息的回調(diào)器(一般不用皮服,見原理圖二)
  • Meaage next:指向下一個(gè)Message用來形成一個(gè)鏈表
  • private static Message sPool:用來緩存處理過的Message楞艾,以便復(fù)用
  • Message obtain():它利用了Message中消息池(sPool

先從Message的創(chuàng)建入手参咙,官方更推薦使用obtain來創(chuàng)建,而不是其構(gòu)造方法硫眯。它的構(gòu)造方法什么也不做蕴侧,只是完成了Message的創(chuàng)建,而obtain可以復(fù)用Message两入,且有很多多種重載净宵。從內(nèi)存、效率來看裹纳,obtain更優(yōu)择葡。

/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
 */
public Message() {
}

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() { // 最常見
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

可見,消息池采用了鏈表結(jié)構(gòu)剃氧,確實(shí)是復(fù)用了Message刁岸。

private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;

private static final int MAX_POOL_SIZE = 50;

下面是Message的回收處理:

/**
 * Return a Message instance to the global pool.
 * <p>
 * You MUST NOT touch the Message after calling this function because it has
 * effectively been freed.  It is an error to recycle a message that is currently
 * enqueued or that is in the process of being delivered to a Handler.
 * </p>
 */
public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 */
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    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++;
        }
    }
}

前面也提到,Message可以自行處理她我,處理的邏輯交給一個(gè)Runnable虹曙,也就是callback,讀者可以自行查閱obtain的其他重載番舆,看看callback賦值的情況酝碳,這里就不帖代碼了椅贱。

Handler:發(fā)送透揣、處理、移除消息

前面已經(jīng)分析過Message的相關(guān)代碼庐镐,該小節(jié)將從發(fā)送Message開始分析禾怠。其實(shí)返奉,也可以通過Handler來創(chuàng)建Message,其內(nèi)部也是調(diào)用Message.obtain來創(chuàng)建Message對(duì)象吗氏。

public final Message obtainMessage() {
    return Message.obtain(this);
}

Handler發(fā)送消息的方式有多種方式芽偏,這里選了一種最常見的,它的調(diào)用流程:

發(fā)送過程.png

源碼如下:

public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0); // 延時(shí)發(fā)送時(shí)間為0
}

// 延時(shí)發(fā)送
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    // 容錯(cuò)
    if (delayMillis < 0) {
        delayMillis = 0;
    }
     // 當(dāng)前時(shí)間加上延時(shí)時(shí)間就是真正發(fā)送Message的時(shí)間
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // Looper創(chuàng)建時(shí)弦讽,MessageQueue也創(chuàng)建了
    // mQueue在Handler創(chuàng)建是就初始化了污尉,代碼在前面已經(jīng)貼過了
    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);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // Message的成員變量,用來存儲(chǔ)Handler的對(duì)象引用
    // 也就是記錄了由哪個(gè)Handler來處理這個(gè)Message
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 將Message插入到MessageQueue中往产,完成發(fā)送
    return queue.enqueueMessage(msg, uptimeMillis);
}

另外被碗,Handlerpost方法也可以發(fā)送Message,且該Message將交給它自身來處理仿村,當(dāng)讀者看過dispatchMessage方法后就明白了锐朴。

public final boolean post(Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r; // 可以回憶下講述Message的那一節(jié)
    return m;
 }

下面是dispatchMessage的源碼,它在Looper.loop方法中被調(diào)用:

public void dispatchMessage(Message msg) {
    // 如果Message.callback不為null蔼囊,就交給它自身處理
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 如果mCallback不為null焚志,交給該Handler處理
        // mCallback是Handler內(nèi)部的一個(gè)接口
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 最后是我們最常見的衣迷,重寫handleMessage
        handleMessage(msg);
    }
}

接著來看看Callback,這個(gè)接口長(zhǎng)啥樣:

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

它也是在Handler創(chuàng)建的時(shí)候初始化的娩嚼,但調(diào)用的構(gòu)造方法不一樣,導(dǎo)致最后的初始化不一樣滴肿。無參的構(gòu)造方法會(huì)導(dǎo)致mCallbacknull岳悟。

public Handler(Callback callback) {
    this(callback, false);
}

最后來看看Handler移除Message的實(shí)現(xiàn),但真正的實(shí)現(xiàn)交給了MessageQueue泼差。

public final void removeMessages(int what) {
    mQueue.removeMessages(this, what, null);
}

public final void removeMessages(int what, Object object) {
    mQueue.removeMessages(this, what, object);
}

MessageQueue:存儲(chǔ)消息的贵少,以message的when排序優(yōu)先級(jí)

Message經(jīng)Handler發(fā)送后到達(dá)MessageQueue,它采用鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)Message堆缘。下面是其構(gòu)造方法:

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    // 如果需要理解為什么loop方法不會(huì)卡住主線程滔灶,可以從這個(gè)本地方法開始入手
    mPtr = nativeInit();
}

MessageQueue中最主要的兩個(gè)方法是nextenqueueMessage

next方法也會(huì)阻塞(當(dāng)消息隊(duì)列為空時(shí)或當(dāng)前時(shí)間還沒到達(dá)Message要處理的時(shí)間點(diǎn)時(shí))但不會(huì)卡住主線程吼肥,它的工作就是根據(jù)需求從消息隊(duì)列中取一個(gè)Message录平。注意,next方法是一個(gè)無限循環(huán)的方法缀皱。

enqueueMessage方法的工作就是將一個(gè)Message插入到消息隊(duì)列且位置是合適的斗这,因?yàn)樗鼤?huì)根據(jù)Message要處理的時(shí)間點(diǎn)進(jìn)行排序,從它的插入操作也可以了解到MessageQueue采用了鏈表啤斗。

由于next方法和enqueueMessage方法的源碼過長(zhǎng)表箭,下面只貼出enqueueMessage排序Message的源碼:

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

關(guān)于阻塞但不會(huì)卡住主線程的問題在下一小節(jié)會(huì)提到。

Looper:從MessageQueue中獲取當(dāng)前需要處理的消息钮莲,并提交給Handler處理

關(guān)于Looper在前面也講述了一部分免钻,現(xiàn)在只剩下loop方法了,很重要崔拥。在它的內(nèi)部也開啟了一個(gè)無限循環(huán)极舔。

for無限循環(huán)中,有一句表明了會(huì)阻塞:

Message msg = queue.next(); // might block

導(dǎo)致它阻塞的原因在MessageQueue.next方法链瓦。當(dāng)消息隊(duì)列退出或正在退出時(shí)姆怪,loop方法會(huì)結(jié)束,也就是跳出無限循環(huán)澡绩。

if (msg == null) {
   // No message indicates that the message queue is quitting.
   return;
}

在這個(gè)循環(huán)里還有一句需要注意的:

msg.target.dispatchMessage(msg);

意思時(shí)交給Handler處理這個(gè)Message稽揭。至此,整個(gè)流程就分析完了肥卡。

面試的時(shí)候可能會(huì)遇到:為什么loop方法會(huì)阻塞主線程但不會(huì)卡住呢?以下的文章也許可以解答你心中的疑問:

異步任務(wù)

  • 邏輯上:以多線程的方式完成的功能需求
  • API上:指AsyncTask

AsyncTask

  • 在沒有AsyncTask之前璃哟,我們用Handler+Thread就可以實(shí)現(xiàn)異步任務(wù)的功能需求
  • AsyncTask是針對(duì)HandlerThread的封裝,使用它編碼更加簡(jiǎn)潔喊递,更加高效
  • AsyncTask封裝了ThreadPool随闪,比直接使用Thread效率要高

相關(guān)API

  • AsyncTack<Params,Progress,Ressult>
    • Params啟動(dòng)任務(wù)執(zhí)行的輸入?yún)?shù),比如HTTP請(qǐng)求的URL
    • Progress后臺(tái)任務(wù)執(zhí)行的百分比
    • Result后臺(tái)執(zhí)行任務(wù)最終返回的結(jié)果骚勘,比如String
  • execute(Params... params):?jiǎn)?dòng)任務(wù)铐伴,開始任務(wù)的執(zhí)行流程
  • onPreExcute():在分線程工作開始之前在UIThread中執(zhí)行,一般用來顯示提示視圖5
  • doInBackground(Params... params):在workerThread中執(zhí)行俏讹,完成任務(wù)的主要工作当宴,通常需要較長(zhǎng)的時(shí)間
  • onPostExecute(Result result):在doInBackground執(zhí)行完后再UIThread中執(zhí)行,一般用來更新界面
  • publishProgress(Progress... values):在分線程中發(fā)布當(dāng)前進(jìn)度
  • onProgressUpdate(Progress... values):在主線程中更新進(jìn)度

AsyncTask在具體的使用過程中也是有一些條件限制的泽疆,主要有以下幾種(摘自《Android開發(fā)藝術(shù)探索》):

  • 1户矢、AsyncTask的類必須在主線程中加載,這就意味著第一次訪問AsyncTask必須發(fā)生在主線程殉疼,當(dāng)然這個(gè)過程在Android 4.1及以上版本中已經(jīng)被系統(tǒng)自動(dòng)完成梯浪。在Android 5.0的源碼中,可以查看ActivityThreadmain方法瓢娜,它會(huì)調(diào)用AsyncTaskinit方法驱证,這就滿足了AsyncTask的類必須在主線程中進(jìn)行加載這個(gè)條件了。
  • 2恋腕、AsyncTask的對(duì)象必須在主線程中創(chuàng)建抹锄。
  • 3、execute方法必須在UI線程調(diào)用荠藤。
  • 4伙单、不要再程序中直接調(diào)用onPreExecuteonPostExecute哈肖、doInBackgroundonProgressUpdate方法
  • 5吻育、一個(gè)AsyncTask對(duì)象只能執(zhí)行一次,即只能調(diào)用一次execute方法淤井,否則會(huì)報(bào)運(yùn)行時(shí)異常
  • 6布疼、在Android 1.6之前,AsyncTask是串行執(zhí)行任務(wù)的币狠,Android 1.6的時(shí)候AsyncTask開始采用線程池里處理并行任務(wù)游两,但是從Android 3.0開始,為了避免AsyncTask所帶來的并發(fā)錯(cuò)誤漩绵,AsyncTask又采用一個(gè)線程來串行執(zhí)行任務(wù)贱案。盡管如此,在Android 3.0以及后續(xù)的版本中止吐,我們?nèi)匀豢梢酝ㄟ^AsyncTaskexecuteOnExecutor方法來并行地執(zhí)行任務(wù)宝踪。

總結(jié)

文章以Handler的創(chuàng)建過程為參照侨糟,簡(jiǎn)單介紹了Handler的原理和源碼。文章末尾對(duì)AsyncTask進(jìn)行了簡(jiǎn)單的介紹瘩燥。讀者有空可以讀一讀《Android開發(fā)藝術(shù)探索》的第10章和第11章秕重。

最后編輯于
?著作權(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
  • 序言:老撾萬榮一對(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

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