Android消息机制详解

use for

相信于此,绝大多数同学都会回答消息机制是android 为了线程间通信而引入的工具。可以轻松的将一个任务切换到handler所在线程执行。android开发规范有规定,不允许于子线程更新ui,这样会触发异常;我们平时使用handler主要都是将子线程切换到主线程中去执行;因此从本质上来来说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。


Q?为何不能在主线程外更新ui呢?

A: 因为Android的UI线程是非线程安全的,应用更新UI,是调用invalidate()方法来实现界面的重绘,而invalidate()方法是非线程安全的,也就是说当我们在非UI线程来更新UI时,可能会有其他的线程或UI线程也在更新UI,这就会导致界面更新的不同步。因此我们不能在非UI主线程中做更新UI的操作。也就是说我们在使用Android中的线程时,要保证: 更新ui都在UI主线程执行.

Q:那为何不将需要更新ui的操作放在UI线程执行呢?

A: 我们都知道在java中,线程存在以下几种基本状态:创建,就绪运行阻塞,死亡。我们的应用启动后,所有的交互都是在UI线程完成的;如果在UI执行延时操作,如常见的网络请求UI线程就会进入阻塞状态;此时用户就无法响应任何操作了;如果此过程超过5秒,就会让程序处于ANR(application not response),这时用户就可能想要和你的应用说声gg了。

Q:Android提供了哪几种线程间通信方式?

A: AsyncTask?,Handler 。为什么AsynTask打了个?呢,我们可以简单看下AsynTask源码,他内部也是接住handler来进行线程间通信的。

Q:MessageQueue存在Targer对象的消息,那和我们正常流程中,由handler传递的消息有什么出入呢?
A: 其实平时我们使用的Message,都是通过Handler发送的,有一些系统消息,他们会直接通过调用MessageQueue发送一个屏障消息,这类消息没有Target,然后配合Handler发送异步消息来使用;当MessageQueue读取到屏障消息后,他们会直接在链表中找到最近的异步消息,直接执行。


feature-要素

  • Message(消息单元)
    定义一个可以发送到Handler的消息;它定义了消息Id,两个额为的int字段和一个额外的object字段(消息处理对象),它们可以不被初始化;虽然它的构造方法是public,但是还是建议我们通过obtain系列函数进行定义。

  • MessageQueue(消息队列)
    存放所有发送的消息队列,单链表结构,供Looper从中读取数据;延时消息是怎么存取的,这个很有趣;

  • Looper(消息读取者)
    永动机;其中有个死循环函数Loop(),不断读取MessageQueue中的消息,交给目标处理;问题来了,既然是个死循环,那不是始终会阻塞Looper所在线程吗。这又是如何解决的。

  • Handler(消息分发以及处理者)
    通过sendMessage系列函数,会将Message传入MessageQueue中;Looper.loop()读取到消息传递给Handler处理。

desc

  1. handler创建前,Looper.loop()执行前;需要保证当前线程Looper有创建,而这个保证即Looper.prepare();主线程由于在1ActivityThread1创建时,已经做过,所以无需执行;
  2. Looper.loop()中有一个死循环,所以线程资源不会释放;在线程运行结束时调用MessageQueue中的quit函数,我们才能释放资源;
  3. Java中,所有非静态成员变量会持有当前对象的引用(不然你又是怎么引用外部类的各种成员变量和函数等);那样我们在Activity中通过new Handler() , 创建的对象会持有当前页面的引用;而我们发送的每个消息不能保证是立即执行,以及迅速执行结束的,handler.sendEmptyMessageDelayed;消息是会持有handler做为他的target,那在这个message在通过msg.target.dispatchMessage(msg);会一直被持有;这样会导致messageQueue->message->handler->activity|fragment;在页面被销毁,声明周期执行到desatory时,activity不会得到释放,从而内存泄漏handler得到消息处理时,如果当前页面已经被销毁,执行Ui更新,又会导致难以预料的问题。
  4. 针对3所提的我们可以按以下两种处理:
    1:页面destory销毁时,调用handler.removeCallbacksAndMessages(null);
    2:通过软引用创建静态Handler对象;

流程解析

android handler流程分析晚上有很多资料;我们这儿简单介绍下:

  • Looper,MessageQueue 就绪;调用Looper.prepare(),其间会向Looper静态线程变量sThreadLocal插入一个当前线程的Looper;在调用Looper构造函数时,我们会初始化MessageQueue,并将mThread设置为当前Thread.currentThread();

  • Looper.prepare()代码块如下:

    public static void prepare() {
            prepare(true);
        }
    
    private static void prepare(boolean quitAllowed) {
          //sThreadLocal->ThreadLocal对象,里面封装了一个map逻辑,key是线程hash值;static 类变量
           if (sThreadLocal.get() != null) {//不允许多次prepare
                throw new RuntimeException("Only one Looper may be created per thread");
           }
           sThreadLocal.set(new Looper(quitAllowed));//设置当前线程的Looper
     }
    

    Looper构造函数,以及MessageQueue构造函数如下:

    private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed); //初始化消息池
            mThread = Thread.currentThread();
     }
    //是否允许退出
    MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed; 
            mPtr = nativeInit(); //线程id
      }
    
  • 接下来我们看下数据插入

    1. sendMessage(Message msg)
    2. sendEmptyMessage(int what)
    3. sendEmptyMessageDelayed(int what, long delayMillis)
    4. sendEmptyMessageAtTime(int what, long uptimeMillis)
    5. sendMessageDelayed(Message msg, long delayMillis)
    6. sendMessageAtTime(Message msg, long uptimeMillis)
    7. sendMessageAtFrontOfQueue(Message msg)

    这以上七个方法,可以通过handler向handler所在线程发送消息;其中1,2,3,4,5都是调用方法6进行执行的;其中方法6中的uptimeMillis取的是系统非休眠时间SystemClock.uptimeMillis()

    我们接下来看下sendMessageAtTime(Message msg, long uptimeMillis):

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue; //将线程变量Looper中的queue取出使用
        if (queue == null) { //queue判空,其实创建handler时,也是必须要Looper初始化结束;queue创建后的
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);//简单判断,交给enqueueMessage函数执行;
    }
    

    7.sendMessageAtFrontOfQueue(Message msg)调用的函数如下:

    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);//简单判断,交给enqueueMessage函数执行;设置执行时间0
        }
    

    据此,我们发现所有消息的发送都是通过MessageQueueenqueueMessage(Message msg, long when)方法;

  • 我们来看下enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; //将msg的target设置为当前handler;这儿可以看出msg和handler是 n:1的关系
        if (mAsynchronous) {//handler是否是异步?默认false;将值赋予msg
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);//可以看到,此处最终调用了eqeue的enqueueMessage方法
    }
    

    那我们看下enqueueMessage函数(handler的消息基本都是通过该函数放入线程MessageQueue中):

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {//handler不能为空
            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) {//mQuitting;子线程消息池我们在线程即将结束时,调用这个mQuitting退出;之后发送的消息都是不会被收入消息池的;所以如果遇到消息没有发送成功,我们可能需要判断是不是looper已经退出了;
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
      
            msg.markInUse();//标志该消息被处理了
            msg.when = when;//设置执行的时间
            Message p = mMessages;//链表头消息
            boolean needWake;//是否需要唤醒线程
            if (p == null || when == 0 || when < p.when) {//表头无消息||即时消息||当前消息执行时间小于表头时间
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                //表头消息替换为放入的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.
                //入队列中,默认不唤醒,仅当头部msg是屏障消息,当前msg是异步消息
                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插入对应节点
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
    
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    
  • 于此我们所有的消息入栈已经看完了;那消息是怎么获取的呢;mBlocked是不是真的代表线程阻塞呢?根据前面的图形介绍,我们知道,Looper中有一个loop函数,他是一个死循环,负责向MessageQueue读取数据,接下来我们来看下这个函数;

    /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop. 在线程结束时,要调用looper.quit()退出
         */
        public static void loop() {
            final Looper me = myLooper(); //静态方法,获取当前线程的Looper;两个工作线程进行通信,需要先在补获线程调用prepare(),并在其run()结束时,调用quit()
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue; //获取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();  //清空远程调用端的uid和pid,用当前本地进程的uid和pid替代
            final long ident = Binder.clearCallingIdentity();
    
            // Allow overriding a threshold with a system prop. e.g.
            // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
            final int thresholdOverride =
                    SystemProperties.getInt("log.looper."
                            + Process.myUid() + "."
                            + Thread.currentThread().getName()
                            + ".slow", 0);
    
            boolean slowDeliveryDetected = false;
    
            for (;;) {
                Message msg = queue.next(); // might block 获取消息可能阻塞线程;我们先分析它,后面的代码下面分析
               ……
            }
        }
    

    MessageQueue.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) { //在调用quit结束loop后,又一次尝试调用prepare后,此时ptr会为0,不支持
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration 默认pendingIdleHandler为0
            int nextPollTimeoutMillis = 0; //需要阻塞时间,-1表示无限阻塞,直到消息入栈调用nativeWake唤醒
            for (;;) {
                if (nextPollTimeoutMillis != 0) {//time不为0存在阻塞
                    Binder.flushPendingCommands();  //native方法,看注释是配合线程长时间阻塞使用,用于释放任何的挂起对象
                }
    
                nativePollOnce(ptr, nextPollTimeoutMillis);//线程阻塞,time阻塞时长
              
                //同步锁
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    //当前消息的目标为屏障消息(消息无target),找寻下一个异步消息执行
                    if (msg != null && msg.target == null) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        //when比当前时间大;需要阻塞
                        if (now < msg.when) {
                            // 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 {
                            //返回这个将要执行的消息;将mBlocked阻塞置false;将当前message置为执行消息后一个
                            // Got a message.
                            mBlocked = false;无需阻塞
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg; //返回消息,退出当前循环
                        }
                    } else {
                        // No more messages.没有消息;无限阻塞,直到新消息入列唤醒它
                        nextPollTimeoutMillis = -1;
                    }
    
                    // Process the quit message now that all pending messages have been handled.
                    if (mQuitting) { //如果执行了退出。调用dispose();
                        dispose();
                        return null;//返回null作为next()执行结果,注意,此时Looper.loop()也会执行结束
                    }       
                    //idleHandlers->idleHandler是指一个线程当前没有需要立即执行的消息,(延时执行or无消息)时,会执行的一个callback;根据上面的分析,只有在next()执行,且没有需要返回消息时执行         
                    if (pendingIdleHandlerCount < 0 
                            && (mMessages == null || now < mMessages.when)) {//延时执行or无消息
                        pendingIdleHandlerCount = mIdleHandlers.size(); //只有调用addIdleHandler加入idle时,count才会增加
                    }
                    //默认0;无idle时,mBlocked阻塞置为true,执行循环 for (;;) {}内部内容
                    if (pendingIdleHandlerCount <= 0) {
                        // 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();//根据queueIdle返回值,决定是否需要执行后移除该idle
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
              
                //执行完PendingIdleHandler后,我门将count置为0,不再执行他
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
              //执行完idle后,可能有消息准备就绪,我们重新计算阻塞时间
                // 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;
            }
        }
    

    总结一下,MessageQueue不断获取待执行消息,并可能阻塞线程(没有message or 待执行messagewhen比当前时间晚);而MessageQueue提供了一个Idle机制,用于在当前线程没有由于没有待执行Message或者延时Message时执行,而addIdleHandler就是用于添加Idle

    我们再回头看下Looper.loop():

     for (;;) {
                Message msg = queue.next(); // might block 刚刚分析上文,当前消息是延时消息或者消息队列为空时,会进行阻塞
                if (msg == null) { //没有消息退出循环,loop结束工作 ; next控制 ;即Mq执行quit退出后,不在执行任何消息
                    // 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);
                }
              //Trace用于追踪一个Message执行的;可以结合TraceView等工具查看,具体请百度吧;而我们的Ui线程所有的ui绘制,事件流执行,等都属于一个消息,可以通过Trace进行跟踪;
                final long traceTag = me.mTraceTag;
                long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
                long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
                if (thresholdOverride > 0) {
                    slowDispatchThresholdMs = thresholdOverride;
                    slowDeliveryThresholdMs = thresholdOverride;
                }
                final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
                final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    
                final boolean needStartTime = logSlowDelivery || logSlowDispatch;
                final boolean needEndTime = logSlowDispatch;
          
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));//跟踪起始
                }
    
                final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
                final long dispatchEnd;
                try {
                    msg.target.dispatchMessage(msg); //分发消息
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; //记录执行结束时间
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);//跟踪结束
                    }
                }
              //Slow?没研究,但也是打印相关日志信息的。。
                if (logSlowDelivery) {
                    if (slowDeliveryDetected) {
                        if ((dispatchStart - msg.when) <= 10) {
                            Slog.w(TAG, "Drained");
                            slowDeliveryDetected = false;
                        }
                    } else {
                        if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                                msg)) {
                            // Once we write a slow delivery log, suppress until the queue drains.
                            slowDeliveryDetected = true;
                        }
                    }
                }
                if (logSlowDispatch) {
                    showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
                }
    
                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();//msg结合obtion()实现对象复用
            }
    

    结合代码来看Looper.loop()所做的事情不多,主要都是用于记录分析Message信息的:

  • 开启一个死循环,将消息读取交给MessageQUeuenext()函数,该函数可能导致线程阻塞;

  • 提供一个Printer接口,记录打印每个Message的执行开始和结束信息;

  • 提供的Trace函数用来记录每个消息的处理信息;

  • 通过msg.target.dispatchMessage(msg)执行消息

  • 通过msg.recycleUnchecked()回收消息,使得Message消息池得到复用;Message是一个链表结构,提供了Message.obtion()方法,用于不断的取链表头对象;在表头空时新建;消息执行完调用的recycleUnchecked会将Message相关消息情况,插入链表头

    至此我们对Android的消息机制发送和读取有了一个完整的了解:下面附上一个简单的流程图(md的流程图绘制,真心累啊。)

对于一个消息创建流程,加入消息队列,MessageQueue简单通过Mq代表了:

image.png

接下来是消息读取的流程图:

image.png

上面流程图中有涉及到一个新的消息概念屏障消息(无target的消息)

我们向消息队列MessageQueue发送一个屏障消息,然后再发送一个异步消息;在我们读取到这个屏障消息的时候,我们会找到链表后的第一个异步消息;这样就能快速执行该异步消息了;

系统有一个postSyncBarrier()用来发送屏障消息,但是被隐藏了;我们可以反射调用或者直接向MessageQueue表头反射插入一个Message,但是不建议这样做;

发送异步消息可以通过:创建Handler时,传入异步参数:

public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);

这样就能发送屏障消息和异步消息了;

在系统源码ViewRootImpl.scheduleTraversals中,为了更快响应UI刷新事件时

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //设置同步障碍,确保mTraversalRunnable优先被执行
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //内部通过Handler发送了一个异步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }

mTraversalRunnable调用了performTraversals执行measure、layout、draw

为了让mTraversalRunnable尽快被执行,在发消息之前调用MessageQueue.postSyncBarrier设置了同步屏障

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,332评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,930评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,204评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,348评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,356评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,447评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,862评论 3 394
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,516评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,710评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,518评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,582评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,295评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,848评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,881评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,121评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,737评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,280评论 2 341

推荐阅读更多精彩内容