Handler使用机制及四个组成部分

     今天我们来了解一下Handler。Android中操作UI控件需要在主线程中进行,为了打破对主线程的依赖(将耗时操作在后台线程执行,而将执行结果在ui线程中操作ui显示),Android引入了Handler消息传递机制。

Handler

     Handler有这样三个特点:
a.允许你去发送并处理一条与Runnable对象和MessageQueue相关联的消息。
b.每一个Handler实例都与一个单独的线程和该线程的MessageQueue相关。
c.当你创建一个新的处理程序时,它将绑定到正在创建的线程的线程/消息队列——从那个点开始,它将向该消息队列传递消息和runnables,并在它们从消息队列中释放时执行它们。
     实际上,根据我的理解,handler就是我们在各线程间处理发送消息数据的一种机制,实现线程间切换的一种方式。
     Handler有两个主要用途:
(1)将消息和runnables作为将来的某个点执行
(2)在不同的线程上执行要执行的操作

Handler线程模型

     先介绍了一下Handler的基本概念,下面就来说一下handler线程模型与一些handler常用的函数以及相关的类。

  • 我们都知道,android的UI操作必须要在主线程中进行,为什么? 因为在多线程中同时执行UI操作是不安全的
  • 可以把全部的操作都放在主线程中么? 不可以,耗时操作需要在后台线程执行,避免ANR
    Handler线程模型.png

         也就是说,如果我们进行了耗时操作,如网络加载图片后,又想显示在我们的ImageView上,那么就需要进行线程间切换,使用handler将后台线程切换到主线程上后,进行UI操作。

Handler常用方法

构造方法

     Handler是Android中实现线程间切换机制的类

  • public Handler() 无参构造,直接new。
    // 若只是执行runnable则不需要覆写handleMessage方法。因为执行runnable会自动忽略handleMessage方法本身。
    Handler handler0 = new Handler();
    // 如果我们要对发送的消息进行操作则需要覆写handleMessage方法。
    Handler handler1 = new Handler(){
         @Override
            public void handleMessage(Message msg) {
                // 获得消息后操作
            }
    }
  • public Handler(Callback callback)
  • public Handler(Looper looper)
  • public Handler(Looper looper,Callback callback) 参数1,looper;参数2,callback回调函数,相当于实现handler抽象方法handleMessage(Message msg);。
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
调用函数
  • post(Runnable r) 将Runnable添加到MessageQueue中。此处要强调一点,如果发送的是runnable则会忽略掉handleMessage方法的执行,即使是发送含有runnable的Message则也会忽略掉handleMessage方法的执行
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
  • postAtTime(Runnable r, long updateMillis) 在指定的时间点运行runnable。
  • postDelayed(Runnable r, long delayMillis) 在延迟一段时间后运行runnable。
    // 此处两个方法执行runnable时机一致
    handler.postDelayed(runnable,2000);
    handler.postAtTime(runnable,System.currentTimeMillis() + 2000);
  • postAtFrontQueue(Runnable r) 将Runnable放在队列最前端执行
  • sendEmptyMessage(int what) 发送一个空消息,参数为int型what(消息执行标识)。
  • sendMessage(Message msg) 发送消息。
  • sendMessageAtTime(Message msg,long updateMillis) 在指定时间点发送消息。
  • sendMessageDelayed(Message, long delayMillis) 在延迟一段时间后发送消息。
  • sendMessageAtFrontOfQueue(Message msg) 将message放在消息队列的最前面发送。
    sendMessage方法与post方法最后执行handleMessage方法或执行runnable的线程就是Handler的创建线程。
取消任务
  • removeCallbacks(Runnable r) 移除当前消息队列中runnable == r的消息。
  • removeCallbacks(Runnable r ,Object token) 移除当前消息队列中target == handler,runnable == r,Object == token 的消息。
  • removeMessages(int what) 取消所有标识为what的消息。
  • removeMessages(int what ,Object token) 取消含有token对象的被what标识的消息。
  • removeCallbacksAndMessages(Object token) 取消含有token的全部消息。

有关Handler常用类,四个组成部分

  1. Message:消息,被传递和处理的数据。其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
         Message作为传递的消息与Handler密不可分,所以此处我们稍微展开讲解一下Message属性与获得方式:
         Message的属性):
  • public int what 传递的消息执行标识,用于标记不同的消息。
  • public int arg1 类似与bundle来进行携带int类型的数据。
  • public int arg2 如果一个arg1还不够用再来个arg2。
  • public Object object 任意对象传递容器。类似于bundle,用于跨进程专递对象
  • Runnable callback 执行的runnable对象。
  • long when 发送的时机。
  • Handler target 所依附的handler,可以理解为通过这个target,这个消息会找到它该去的地方,然后执行对消息的操作也就是handleMessage方法。
         Message的获得(通常使用Message.obtain()或Handler.obtainMessage()方法来从消息池中获得空消息对象,以节省资源):
  • Message msg0 = new Message();
  • Message msg1 = Message.obtain();
  • public static Message obtain(Handler h)
  • public static Message obtain(Handler h, int what)
  • public static Message obtain(Handler h, int what, Object obj)
  • public static Message obtain(Handler h, int what, int arg1, int arg2)
  • public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj) 参数1:targetHandler,参数2:what标识符,参数3:int型数据,参数4:int型数据,参数5:Object对象。
  • public static Message obtain(Message orig) 相当于拷贝某个Message而获得Message。
  • public static Message obtain(Handler h,Runnable callback) 传入targetHandler与需要回调执行的runnable。
  1. Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。Handler类的主要作用:(有两个主要作用)
    a. 在工作线程中发送消息;
    b. 在主线程中获取、并处理消息。
  2. MessageQueue:消息队列,本质是一个数据结构,用来存放Handler发送过来的消息,并按照FIFO规则(先进先出)执行。当然,存放Message并非实际意义的保存,而是将Message串联起来,等待Looper的抽取。
  3. Looper:消息泵或循环器,不断从MessageQueue中抽取Message。因此,一个MessageQueue需要一个Looper。
  4. Thread:线程,负责调度整个消息循环,即消息循环的执行场所。


    Handler简易关系.png

         此处我们画一个简易图来逐步了解Handler的运行机制。图中Handler(左右两个handler为同一个handler)可以与MessageQueue以及Looper在同线程中,也可在不同线程中。

Handler进程间切换举例

     到这里,我想我们脑中有了一个对Handler大致的理解,渐渐的看清了它的样子,那么接下来,我们来写一些代码,来看看handler究竟是怎样运行,工作的。

主线程创建handler

     为了节约版面,我们不列举每一种方法,只举例有代表性的方法,其他的都会提交到我的github中Handler传送门,大家有需要可以去下载研究。

  • 我们先在主线程中创建handler而在非主线程中来调用post(Runnable r),观察是否最后runnable运行的线程为主线程(代码段中post(View v)方法为button点击调用函数)。
public void post(View v) {
        Log.e("tag", " ------>>> " + "点击Post按钮 " + getThreadName());
        // 在主线程创建handler
        final Handler handler = new Handler();
        // 创建一个非主线程的新线程
        Thread thread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Log.e("tag", " ------>>> " + "执行非主线程runnable " + getThreadName());
                        // 此处可以进行一些耗时操作,比如加载网络图片或读写文件等操作。
                        // handler.post(runnable) 去观察是否回到主线程
                        handler.post(
                                new Runnable() {
                                    @Override
                                    public void run() {
                                        Log.e("tag", " ------>>> " + "post runnable" + getThreadName());
                                    }
                                }
                        );
                    }
                }
        );
        // 运行该线程
        thread.start();
    }

     运行结果:

05-29 14:58:54.234 6268-6268/com.perry.handler E/tag:  ------>>> 点击Post按钮  主线程 
05-29 14:58:54.238 6268-6320/com.perry.handler E/tag:  ------>>> 执行非主线程runnable  非主线程 
05-29 14:58:54.253 6268-6268/com.perry.handler E/tag:  ------>>> post runnable 主线程 

     可以看到我们的handler成功的完成了线程间切换的任务,将需要执行的runnable在非主线程进行耗时操作后回到handler所创建的线程也就是主线程运行。

  • 再来看一下sendMessage(Message msg)方法,后执行handleMessage(Message msg)方法的方法体,是否完成了线程间切换。
 public void sendMessage(View view) {
        Log.e("tag", " ------>>> " + "点击sendMessage按钮 " + getThreadName());
        final Handler handler = new Handler() {
            // 此处处理msg需要覆写该方法
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 0:
                        Log.e("tag", " ------>>> " + "sendMessage" + getThreadName() + " 参数 = " + msg.obj);
                        break;
                }

            }
        };
        // 创建一个非主线程的新线程
        Thread thread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Log.e("tag", " ------>>> " + "执行非主线程runnable " + getThreadName());
                        // handler.sendMessage(msg) 去观察是否回到主线程
                        handler.sendMessage(Message.obtain(handler, 0, "obj参数"));
                    }
                }
        );
        // 运行该线程
        thread.start();
    }

     运行结果:

05-29 15:08:53.077 6682-6682/com.perry.handler E/tag:  ------>>> 点击sendMessage按钮  主线程 
05-29 15:08:53.078 6682-6729/com.perry.handler E/tag:  ------>>> 在非主线程执行 handler.sendMessage(msg)  非主线程 
05-29 15:08:53.096 6682-6682/com.perry.handler E/tag:  ------>>> sendMessage 参数 = obj参数 主线程 

     可以看到我们的sendMessage(Message msg)方法也成功的完成了线程间切换的任务,将需要执行的对msg的操作在非主线程进行耗时操作后回到主线程运行handleMessage(msg)的方法体。

  • 之前说过如果发送的是runnable则会忽略掉handleMessage方法的执行,即使是发送含有runnable的Message则也会忽略掉handleMessage方法的执行,那么来证明一下。
public void sendMessageCallback(View view) {
        Log.e("tag", " ------>>> " + "点击sendMessageCallback按钮 ");
        final Handler handler = new Handler() {
            // 此处处理msg需要覆写该方法
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // handleMessage 执行方法体
                Log.e("tag", " ------>>> " + "sendMessage  handleMessage");
            }
        };
        // 发送一个Message
        handler.sendMessage(
                // 创建含有Runnable callback的msg
                Message.obtain(
                        handler,
                        new Runnable() {
                            @Override
                            public void run() {
                                // callback 执行方法体
                                Log.e("tag", " ------>>> " + "sendMessage  Runnable");
                            }
                        }

                )
        );
    }
    // 执行结果
05-31 14:36:54.975 2083-2083/com.perry.handler E/tag:  ------>>> 点击sendMessageCallback按钮 
05-31 14:36:54.992 2083-2083/com.perry.handler E/tag:  ------>>> sendMessage  Runnable

     为了看的更清晰,这里去掉了线程的切换与打印,都是在主线程里发送和接收的消息,我们最终发现,执行了runnable的方法体,未执行handleMessage的方法体,所以只要有runnable的存在就会忽略handleMessage方法体的执行
     说到这为止,我们都是从ui线程创建handler最后返回ui线程执行,那如果我们不想在ui线程执行呢?怎么办?

后台线程创建handler

     前面在介绍handler时由一句话sendMessage方法与post方法最后执行handleMessage方法或执行runnable的线程就是Handler的创建线程。所以先写一个在后台创建的handler试试水,看看会不会如我们期待一样,回到后台线程执行。

public void postBackgroundThread(View view) {
        Log.e("tag", " ------>>> " + "点击postBackgroundThread按钮 " + getThreadName());
        Thread thread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Log.e("tag", " ------>>> " + "执行非主线程runnable " + getThreadName());
                        Handler handler = new Handler();
                        handler.post(
                                new Runnable() {
                                    @Override
                                    public void run() {
                                         Log.e("tag", " ------>>> " +"postBackgroundThread " + getThreadName());
                                    }
                                }
                        );
                    }
                }
        );
        thread.start();
    }

     运行一下,结果发现,程序崩溃,讲道理我们就是在非UI线程创建了个handler然后post了一下,为什么会崩溃呢?日志如下:

05-30 15:26:07.446 711-711/com.perry.handler E/tag:  ------>>> 点击postBackgroundThread按钮  主线程 
05-30 15:26:07.486 711-773/com.perry.handler E/tag:  ------>>> 执行非主线程runnable  非主线程 
05-30 15:26:07.490 711-773/com.perry.handler E/AndroidRuntime: FATAL EXCEPTION: Thread-18868
                                                               Process: com.perry.handler, PID: 711
                                                               java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
                                                                   at android.os.Handler.<init>(Handler.java:209)
                                                                   at android.os.Handler.<init>(Handler.java:123)
                                                                   at com.perry.handler.OnBackgroundActivity$2.run(OnBackgroundActivity.java:71)
                                                                   at java.lang.Thread.run(Thread.java:818)

     到这里,我们可以清楚的看到,不能在非主线程中创建handler时不调用Looper.prepare()方法。接下来,就看看,如何正确的在非主线程中创建handler。
     1.手动进行Looper.prepare()及Looper.loop()操作。

public void postNoCrash(View view) {
        Log.e("tag", " ------>>> " + "点击postNoCrash按钮 " + getThreadName());
        Thread thread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Log.e("tag", " ------>>> " + "执行非主线程runnable " + getThreadName());
                        Looper.prepare();

                        Handler handler = new Handler();
                        handler.post(
                                new Runnable() {
                                    @Override
                                    public void run() {
                                        Log.e("tag", " ------>>> " + "postNoCrash " + getThreadName());
                                        // 使用后需要退出looper避免线程阻塞
                                        Looper.myLooper().quit();
                                    }
                                }
                        );

                        Looper.loop();
                    }
                }
        );
        thread.start();
    }
    // 执行Log日志
05-30 16:40:14.660 6368-6368/com.perry.handler E/tag:  ------>>> 点击postNoCrash按钮  主线程 
05-30 16:40:14.672 6368-6426/com.perry.handler E/tag:  ------>>> 执行非主线程runnable  非主线程 
05-30 16:40:14.694 6368-6368/com.perry.handler E/tag:  ------>>> postNoCrash  非主线程 

     2.利用HandlerThread类进行创建。HandlerThread继承Thread,它会启动一个带有looper的新线程,这样我们就可以在非UI线程中创建handler,但是必须要调用start()方法。

public void handleThread(View view) {
        final HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();
        Log.e("tag", " ------>>> " + "点击handleThread按钮 " + getThreadName());
        Thread thread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        Log.e("tag", " ------>>> " + "执行非主线程runnable " + getThreadName());
                        Handler handler = new Handler(handlerThread.getLooper());
                        handler.post(
                                new Runnable() {
                                    @Override
                                    public void run() {
                                        Log.e("tag", " ------>>> " +"handleThread " + getThreadName());
                                    }
                                }
                        );
                    }
                }
        );
        thread.start();

    }
    // 执行Log日志
05-30 16:40:23.301 6368-6368/com.perry.handler E/tag:  ------>>> 点击handleThread按钮  主线程 
05-30 16:40:23.311 6368-6428/com.perry.handler E/tag:  ------>>> 执行非主线程runnable  非主线程 
05-30 16:40:23.323 6368-6368/com.perry.handler E/tag:  ------>>> handleThread  非主线程 

     我们的handler的线程间切换的方法介绍的差不多了,无论是在主线程还是在非主线程中进行线程间切换,而且也印证了我们那句话sendMessage方法与post方法最后执行handleMessage方法或执行runnable的线程就是Handler的创建线程。但是还有一个小东西我们要介绍一下,就是MessageQueue的IdleHandler()接口。

IdleHandler()接口

     该接口的用途是MessageQueue通过实现该方法,将queueIdle()方法的方法体,添加到消息队列里面去,而不使用handler就可以实现。

 public void idleHandler(View view) {
        Log.e("tag", " ------>>> " + "点击idleHandler按钮 " + getThreadName());
        Looper.myQueue().addIdleHandler(
                new MessageQueue.IdleHandler() {
                    @Override
                    public boolean queueIdle() {
                        Log.e("tag", " ------>>> " + "执行非主线程runnable " + getThreadName());
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Log.e("tag", " ------>>> " + "idleHandler " + System.currentTimeMillis());
                        makeToast("IdleHandler " + getThreadName());
                        return false;
                    }
                }
        );
    }

     IdleHandler()是在消息队列空闲的时候,也就是说所有的其他消息是需要延时执行时,会执行该方法的方法体。返回值为false时则在空闲的时候会执行一次该方法体,执行一次后不会再执行,而返回true时,若执行一次后,消息队列依旧空闲则还会再执行该方法体。IdleHandler()是在消息队列空闲的时候执行的,所以在当IdleHandler()执行之后的方法,比如是在1s延迟后执行,但是IdleHandler()在这段时间内被执行了而需要耗时2s,则它之后的消息要在2s之后才执行而不是原有的1s之后执行。所以,在IdleHandler()方法中不要做太耗时的操作。
     为了举例证明,IdleHandler()是在消息队列空闲的时候执行的,我们这里先点击了sendMessageDelayed按钮延时1s进行打印log,然后马上点击了idleHandler按钮其中做了2s线程休眠,对比一下执行时间log,结果如下:(想要自己手动实验的小伙伴Handler传送门送给你,可以给我个小星星!)

05-30 17:05:20.344 9685-9685/com.perry.handler E/tag:  ------>>> 点击sendMessageDelayed按钮 1527671120344
05-30 17:05:20.828 9685-9685/com.perry.handler E/tag:  ------>>> 点击idleHandler按钮  主线程 
05-30 17:05:20.836 9685-9685/com.perry.handler E/tag:  ------>>> 执行非主线程runnable  主线程 
05-30 17:05:22.881 9685-9685/com.perry.handler E/tag:  ------>>> IdleHandler  主线程 
05-30 17:05:22.881 9685-9685/com.perry.handler E/tag:  ------>>> idleHandler 1527671122881
05-30 17:05:22.885 9685-9685/com.perry.handler I/Choreographer: Skipped 121 frames!  The application may be doing too much work on its main thread.
05-30 17:05:22.923 9685-9685/com.perry.handler E/tag:  ------>>> sendMessageDelayed 参数 = obj参数 主线程 
05-30 17:05:22.923 9685-9685/com.perry.handler E/tag:  ------>>> sendMessageDelayed方法体执行 1527671122923
取消任务

     如何发送消息我们都列举完了,接下来我们来取消任务。

public void removeCallbacks(View view) {
        Log.e("tag", " ------>>> " + "removeCallbacks按钮 " + getThreadName());
        Handler handler = new Handler();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Log.e("tag", " ------>>> " + "postDelayed removeCallbacks " + getThreadName());
            }
        };
        handler.postDelayed(runnable, 1000);
        handler.removeCallbacks(runnable);
    }
    
    // log日志
    05-31 11:27:18.791 25879-25879/com.perry.handler E/tag:  ------>>> removeCallbacks按钮  主线程

     这里点击按钮后只显示了一个点击removeCallbacks按钮的log,即使再等了10s也仍未见到runnable中的log日志,成功的移除了该runnable任务。而其他的remove方法也都大同小异,不过是更详细的定位了,到底是哪个runnable或Message,想了解的小伙伴请看Handler传送门

退出Looper

     后台线程不需要的时候,这时后台looper我们也不需要了,或者说我们在某个事件后或某个时间点不需要后续的消息进行发送到消息队列中或执行消息,我们可以通过handlerThread.quit()方法或handlerThread.quitSafely(),方法来退出looper。

public void handlerThreadQuit(View view) {
        Log.e("tag", " ------>>> " + "handlerThreadQuit按钮 " + getThreadName() + System.currentTimeMillis());
        final HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();
        final Handler handler = new Handler(handlerThread.getLooper());
        Thread t = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        // 首先延迟1s发送一条消息
                        handler.postDelayed(
                                new Runnable() {
                                    @Override
                                    public void run() {
                                        makeToast("postDelayed 1s runnable");
                                    }
                                }, 1000
                        );

                        // handler执行runnable进行退出looper
                        handler.post(
                                new Runnable() {
                                    @Override
                                    public void run() {
                                        handlerThread.quit();
                                        makeToast("HandlerThread Loop quit");
                                    }
                                }
                        );
                    }
                }
        );
        t.start();
    }

     quit方法执行后handler将不再接收和执行任何消息,而quitSafely执行后,会先看消息是否过线,如图Handler简易关系.png中消息是否可派发分割线,不可派发为未过线,可派发为过线,过线的消息则可继续执行,未过线的消息则不可执行。且执行quitSafely方法需要在API18以上。

自定义Handler

     写了这么多但回头一看发现,很多方法内部报黄。原来是因为handler内部持有外部类的引用,造成内存无法释放而泄漏。那如何解决呢?这就需要我们来自定义Handler了。

image.png

     此处我们只要用弱引用,引用我们所需要的资源,就不会发生泄漏了,具体代码见Handler传送门,记住界面销毁要适时的取消任务哦!
image.png

     在平时的使用中,只要你认真的看过了这篇文章并动手稍加实践,那一定可以很好的掌握handler的原理及用法,若想要深入学习了解,就需要去分析handler的源码,也就是我的下一篇文章handler源码分析。
     下一篇:Handler源码分析

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

推荐阅读更多精彩内容