Android 中 Handler 的基本使用

一、前言:

Android中的消息机制是通过Handler来实现的。随着EventBus和RxJava等依托观察者模式的消息传递机制的出现,当前在Android开发中Handler的使用已经不如之前那么重要,但是Android系统所提供的Handler中的各种编程思路和设计方案,对我们在编程思想的提升还是有很大益处的。

gitHub 地址:https://github.com/lyyRunning/ThreadDemo0629.git

1.Handler是什么?

Handler是Android给我们提供用于更新UI的一套机制,也是一套消息处理机制。我们用它可以发送消息,也可以用它处理消息。在Android开发中有着非常重要的地位。

2.为什么要使用Handler?

当一个应用程序运行时,它会创建一个进程。这个进程就是我们的主线程(UI线程&Activity Thread) 。在主线程中,会默认为我们在系统中默认创建一个Looper,这个Looper会与我们的Message Queue 和 主线程有一定联系。 在main线程中,主要是运行一个Message Queue,管理着顶级的应用程序(Activity,Boardcast Receiver…)这些顶级应用程序在默认情况下都会在主线程中创建。这就是为什么我们需要在主线程中更新UI。

Android在设计的过程中,就封装了一套消息创建、传递、处理的机制。如果不遵循这样的机制,是没有办法更新UI信息的,会抛出异常信息。

非主线程更新UI的后果

我们可以尝试在一个新的线程中更新UI,会发现程序崩溃了。查看Logcat可以看到这样的一句提示

Only the original thread that created a view hierarchy can touch its views.

这告诉我们,在实际开发中,我们需要遵循Google为我们设定的这样的机制。

那么如何在其他线程达到更新UI的目的呢?使用Handler就是其中一种办法。

3.Handler的作用

根据Android Developer网站上的描述,Handler主要有两个用途

  • 定时地去发送一个Message或Runnable对象
  • 可以跳转到另一个线程中去执行一些操作

4.Handler的使用方式

关于Message

参数

  • public int arg1(arg2):如果只需要存储几个整型数据,arg1 和 arg2是setData()的低成本替代品。

  • public Object obj: 发送给接收者的任意对象。当使用Message对象在线程间传递消息时,如果它包含一个Parcelable的结构类(不是由应用程序实现的类),此字段必须为非空(non-null)。其他的数据传输则使用setData(Bundle)方法。

  • public Messenger replyTo:用户自定义的消息代码,这样接受者可以了解这个消息的信息。每个handler各自包含自己的消息代码,所以不用担心自定义的消息跟其他handlers有冲突。

方法

另外 ,用Message来传递还可通过Message的setData(Bundle)方法来传递。获取时调用getData()方法。与sendData相似的还有peekData。

  • public void setData(*Bundle data):设置一个任意数据值的Bundle对象。如果可以,使用arg1和arg2域发送一些整型值以减少消耗。

  • public Bundle peekData():与getData()相似,但是并不延迟创建Bundle。如果Bundle对象不存在返回null。

  • public Bundle getData():获取附加在此事件上的任意数据的Bundle对象,需要时延迟创建。通过调用setData(Bundle)来设置Bundle的值。需要注意的是,如果通过Messenger对象在进程间传递数据时,需要调用Bundle类的Bundle.setClassLoader()方法来设置ClassLoader,这样当接收到消息时可以实例化Bundle里的对象。

  • public static Message obtain(): 从全局池中返回一个新的Message实例。在大多数情况下这样可以避免分配新的对象。

5.handleMessage方法

handleMessage方法用于接收Message对象并进行相应的处理,对应Handler的sendMessage方法。

使用时应在handler中重写此方法。当在其他线程调用sendMessage方法时,handleMessage方法便会被回调,并携带sendMessage方法调用者在Message中存入的信息。当我们想要在其他线程更新UI时,就可以用主线程中创建的Handler调用sendMessage方法,然后在该Handler重写的handleMessage方法中做相应的处理。

比如此处,我们在handleMessage方法中进行更新TextView的操作,并把Message的arg1作为文本的内容。

private Handler mHandler = new Handler(){
    public void handleMessage(Message msg){
        mTextView.setText(""+msg.arg1+"-"+msg.arg2);
    };
};

new Thread(){
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
         Message message = Message.obtain();
            message.arg1 = 88;
            mHandler.sendMessage(message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}.start();
6.post方法

刚刚的异常我们已经看到,那如何才能使用Handler在这个新建的线程更新UI呢?

我们可以这样做:在主线程中创建一个Handler。然后在子线程中,我们可以调用Handler的post方法,并向其中传递一个Runnable为参数,在Runnable中更新UI即可。

private TextView mTextView;
private Handler mHandler = new Handler();

mTextView = findViewById(R.id.tv_text);
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mTextView.setText("update");
                }
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();
7.postDelayed方法

postDelayed与post方法非常接近,仅仅是参数多了一个long类型的参数delayMills。它与post的区别就是它会在delayMills这段时间之后再去执行Runnable的方法,也就是延迟执行。

有时候我们需要定时的完成一些事情(比如定时更换TextView的文字)时,就可以利用它延迟执行的这一特点来实现。做法是分别在主函数中以及它所执行的Runnable中postDelayed一段时间。这样就可以达到定时地完成某件任务的工作。

比如下例就简单地实现了每隔一秒切换一次TextView文字的作用。

8.removeCallbacks方法

比如我们这里有个定时更新TextView的文本的代码,如果想要按下按钮,停止定时更换文本,就可以通过removeCallbacks方法,传入该Runnable来中止消息。

二、Handler运行机制

Handler的消息机制如下图所示,主要包含两个消息队列,一个是消息的回收队列,另一个是Message Q队列。

1. 消息的回收队列:消息回收队列是为Handler提供消息对象的,当Handler需要发送消息时,首先从消息回收队列中获取已被清空数据的消息对象,若消息对队列中此时没有消息对象,则创建新的消息对象。当消息对象被使用后,不会直接被当做垃圾回收,而是会进入消息的回收队列,在该队列中会将消息对象上的所有数据清空,之后在队列中等待被使用。

2. 获取消息 :直接通过以下两种方式中的任一种获取消息。

 Message message = Message.obtain();
  message.what = MAIN_UI;
  message.obj = "main发送消息的线程名称" 

3. 创建Handler对象: 直接通过如下方式创建Handler对象即可。该Handler默认使用Android提供的Looper。

 Handler handler= new Handler();

4. 发送消息:通过如下等方式进行消息的发送。

handlerUI.sendEmptyMessage(11);//立刻发送空的消息
handlerUI.sendMessage(message);//立刻发送携带数据的消息
handlerUI.sendMessageDelayed(message,1000);//延迟1000ms后发送携带数据的消息

5. Message Q队列:消息队列,用来管理消息,会根据消息的执行时间对消息进行排序。并通过Looper.loop对消息进行轮询取出,当队列中有消息时就将消息取出交给Handler的handlerMessage()回调进行消息的处理,当队列中没有消息时就进行阻塞等待,这种机制又被称作等待唤醒机制。Android中的等待唤醒机制是使用的Linux内核的管道流机制。

6. 消息的处理:通过Looper.loop()取出消息队列中待处理的消息,通过handler的handlerMessage()回调进行消息的处理。处理完成的消息对象会进入消息的回收队列进行回收。

image.png

原理解析:
Handler 创建完毕后,这个时候内部的 Looper 以及 MessageQueue 就可以和 Handler 一起协调工作了,然后通过 Handler 的 post 方法将一个 Runnable 投递到 Handler 内部的 Looper 中去处理,也可以通过 Handler 的 send 方法发送一个消息,这个消息同样会在 Looper 中去处理。其实 post 方法最终也是通过 send 方法发送消息的。

接下来看一下 send 的工作过程:当 Handler 的 send 方法被调用时,它会调用 MessageQueueMessage 方法将这个消息放入消息队列中,然后 Looper 发现有新消息到来时,就会处理这个消息,最终消息中的 Runnable 或者是 Handler 的 handlerMessage 方法会被调用。注意 Looper 是运行在创建 Handler 所在的线程中的,这样一来 Handler 中的业务逻辑就被切换到创建 Handler 所在的线程中去执行了。

三、使用Handler向主线程发送消息

通过handler向主线程发送消息是Handler使用过程中最常规的一种方式,也是最普遍的一种使用方式。当创建Handler对象时,若使用空参构造创建,则创建出的Handler默认使用UI线程中的Looper,这个时候通过Looper.loop()取出的消息在handlerMessage()中进行处理时,是在UI线程中进行处理的。这也就是为什么大家常说:Handler是运行在主线程中的。

这又分成两种情况:

1. 主线程->主线程 发送消息:在主线程中通过创建好的handler调用sendMessage()方法发送消息即可。

2. 子线程->主线程 发送消息:在子线程中通过创建好的handler调用sendMessage()方法发送消息即可。

本次以主线程和子线程向主线程发送消息为例:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn1)
    Button btn1;
    @BindView(R.id.btn2)
    Button btn2;
    @BindView(R.id.tv_message)
    TextView tvMessage;


    private static final int HANDLER_UI = 1;
    private static final int MAIN_UI = 2;


    /**
     * 运行在主线程的Handler:使用Android默认的UI线程中的Looper
     */
    public Handler handlerUI = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case HANDLER_UI:
                    String strData = (String) msg.obj;
                    tvMessage.setText(strData);
                    break;
                case MAIN_UI:
                    String strData2 = (String) msg.obj;
                    tvMessage.setText(strData2);
                    break;
                default:
                    break;
            }
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        tvMessage.setText("主线程发送 Handler");

    }

    @OnClick({R.id.btn1, R.id.btn2, R.id.btn3})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn1:
                //主线程发送
                Message message = Message.obtain();
                message.what = MAIN_UI;
                message.obj = "main发送消息的线程名称:" + Thread.currentThread().getName();
                handlerUI.sendMessage(message);

                break;
            case R.id.btn2:
                //子线程发送
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = Message.obtain();
                        message.what = HANDLER_UI;
                        message.obj = "子线程发送消息的线程名称:" + Thread.currentThread().getName();
                        handlerUI.sendMessage(message);

                    }
                }).start();
                break;

            case R.id.btn3:
                ThreadActivity.launch(MainActivity.this);
                break;
            default:
        }
    }
}

上述代码有一定的隐患,因为如此使用Handler会导致内存泄露,此处是为了举例简洁才如此使用,正常使用Handler时要避免这种方式,解决这种导致内存泄露可通过——WeakReference(弱引用),后面会详细介绍。

四、使用Handler向子线程发送消息

上述说的是Handler常规的使用方式,但有时因为业务需求,需要向子线程发送消息。这时候就需要寻找突破点了,仔细观察其实很容易发现,消息机制最核心的点就是消息的Looper机制,UI线程之所以能够进行消息的实现是因为Android默认提供了一个Looper,含有Looper的线程被称为UI线程,UI线程和子线程之间唯一的区别就是一个有Looper另外的一个没有。既然突破点已经找到,那直接创建一个含有Looper的子线程,即可实现向该子线程发送消息。那如何创建一个含有Looer的子线程能?需要两步:

  1. 在创建的子线程的run()方法中进行Looper相关的配置。

  2. 在创建Hander时的构造方法中传入1线程中的Looper。

代码实现如下:

public class ThreadActivity extends AppCompatActivity {

    @BindView(R.id.tv_message)
    TextView tvMessage;
    @BindView(R.id.btn1)
    Button btn1;
    @BindView(R.id.btn2)
    Button btn2;
    private Handler threadHandler;
    private static final int HANDLER_THREAD = 3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tvMessage.setText("子线程处理Handler,原始用法");
        createLooperHandler();
    }

    @OnClick({R.id.btn1, R.id.btn2, R.id.btn3})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn1:
                break;
            case R.id.btn2:
                //子线程发送
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = Message.obtain();
                        message.what = HANDLER_THREAD;
                        message.obj = "子线程发送消息的线程名称:" + Thread.currentThread().getName();
                        threadHandler.sendMessage(message);

                    }
                }).start();
                break;
            case R.id.btn3:
                ThirdActivity.launch(ThreadActivity.this);
                break;

            default:
        }
    }


    /**
     * 创建一个可以包含looper的子线程,并开启
     */
    private void createLooperHandler() {
        MyThread myThread = new MyThread();
        myThread.start();

        //注释1
        SystemClock.sleep(100);
        threadHandler = new Handler(myThread.threadLooper) {
            @Override
            public void handleMessage(final Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case HANDLER_THREAD:

                        //添加上当前线程名称
                        final String thrMsg = (String) msg.obj + "\n 收到的线程:" + Thread
                                .currentThread().getName();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {

                                tvMessage.setText(thrMsg);
                            }
                        });

                        break;

                    default:
                        break;
                }
            }
        };
    }

    public static void launch(Context context) {
        Intent intent = new Intent(context, ThreadActivity.class);
        context.startActivity(intent);
    }


    private class MyThread extends Thread {

        private Looper threadLooper;

        @Override
        public void run() {
            Looper.prepare();
            threadLooper = Looper.myLooper();
            Looper.loop();
        }
    }
}

通过上面的方法,确实可以实现向子线程发送消息,但是其实还是有隐患的,眼尖的人已经看到上面81行(注释1)处的代码了,为什么要进行睡眠100毫秒,其实是因为MyThread在调用start方法后,代码向下运行,进行了创建Handler的操作,在创建Handler时需要在构造方法中传入MyThread的Looper,而只有当MyThread开启调用run()方法后才会创建出Looper,两者操作是在不同线程中,所以不能确定谁会先被执行。故,如果不进行睡眠的话,就会概率性出现空指针异常。那如何才能保证既可以向子线程发送消息,主线程又不用出现睡眠等待呢?其实android也知道这种情况,所以向我们提供了一个类HandlerThread来解决上述问题。

五、HandlerThread的使用

 HnadlerThread是Thread的子类,是专门向子线程发送消息使用的。使用步骤如下:

public class ThirdActivity extends AppCompatActivity {

    @BindView(R.id.tv_message)
    TextView tvMessage;
    @BindView(R.id.btn1)
    Button btn1;
    @BindView(R.id.btn2)
    Button btn2;
    private Handler handlerThreadHandler;
    private static final int HANDLER_THREAD = 3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tvMessage.setText("子线程处理Handler,正常用法");
        createLooperHandler();
    }

    @OnClick({R.id.btn1, R.id.btn2})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn1:
                break;
            case R.id.btn2:
                //子线程发送
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = Message.obtain();
                        message.what = HANDLER_THREAD;
                        message.obj = "发送的线程:" + Thread.currentThread().getName();
                        handlerThreadHandler.sendMessage(message);

                    }
                }).start();
                break;

            default:
        }
    }


    /**
     * 创建一个可以包含looper的子线程,并开启
     */
    private void createLooperHandler() {
        HandlerThread handlerThread = new HandlerThread("handler_name1");
        handlerThread.start();

        handlerThreadHandler = new Handler(handlerThread.getLooper()) {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case HANDLER_THREAD:
                        //添加上当前线程名称
                        final String thrMsg = (String) msg.obj + "\n 收到的线程:" + Thread
                                .currentThread().getName();


                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                tvMessage.setText(thrMsg);
                            }
                        });
                        break;

                    default:
                        break;
                }
            }
        };
    }

    public static void launch(Context context) {
        Intent intent = new Intent(context, ThirdActivity.class);
        context.startActivity(intent);
    }
}

六、Handler内存泄露

Handler使用过程中,我们需要特别注意一个问题,那就是Handler可能会导致内存泄漏。

具体原因如下:

  • Handler的生命周期与Activity不同,Handler会关联Looper来管理Message Queue。这个队列在整个Application的生命周期中存在,因此Handler不会因Activity的finish()方法而被销毁。

  • 非静态(匿名)内部类会持有外部对象,当我们这样重写Handler时它就成为了一个匿名内部类,这样如果调用finish方法时Handler有Message未处理的话,就会导致Activity不能被销毁。

解决方法:

  1. 可以在外部新建一个类,这样便可解决这个问题。但这样无疑过于麻烦了,内部类更方便些。
  2. 可以同时使用静态内部类和弱引用,当一个对象只被弱引用依赖时它便可以被GC回收。

注意:要static和弱引用要同时使用,否则由于非静态内部类隐式持有了外部类Activity的引用,而导致Activity无法被释放

public static class MyHandler extends Handler {
        WeakReference<Activity> mWeakReference;
 
        public MyHandler(Activity activity) {
            mWeakReference = new WeakReference<Activity>(activity);
        }
 
        @Override
        public void handleMessage(Message msg) {
            Activity activity = mWeakReference.get();
            if (activity == null) {
                return;
            }
 
            switch (msg.what) {
                case 101:
                    adapter.notifyDataSetChanged();
                    break;
 
                default:
                    break;
            }
        }
    }

七、总结

  1. Looper 中还有一个特殊概念,那就是 ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的 Looper。当然需要注意的是,线程是默认没有 Looper 的,如果需要使用 Handler 就必须为线程创建 Looper。

  2. 主线程之所以可以接收Handler消息,是因为主线程在启动时,已经创建了Looper对象,这也是在主线程默认可以使用 Handler 的原因。

  3. Handler 的工作主要包含消息的发送和接受过程。消息的发布主要post 的一系列方法以及 send 的一系列方法来实现的,post 的一系列方法最终通过 send 的一系列方法来实现的。

八、原理总结:

1. ThreadLocal 的工作原理:

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
对于 Handler 来说,它需要获取当前线程的 Looper,很显然 Looper 的作用域就是线程,并且不同的线程具有不同的 Looper,这个时候通过 ThreadLocal 就可以轻松实现 Looper 在线程中的存取。

2. 消息队列的工作原理:

消息队列在 Android 中指的是 MessageQueue,MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法分别为 enqueueMessage 和 next,其中

  • enqueueMessage的作用是往消息队列中插入一条消息;
  • next 的作用是从消息队列中取出一条消息并将其从消息队列中移除;

尽管,MessageQueue叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。

3. Looper 的工作原理:

Looper在 Android 的消息机制中扮演着消息循环的角色,具体来说就是不停地从MessageQueue 中查看是否有新的消息,如果有新的消息就会立刻处理,否则就一直阻塞在那里。

Handler 的工作需要 Looper,没有 Looper 的线程就会报错,那么如何为一个线程创建 Looper 呢?其实很简单,通过 Looper.prepare()即可为当前线程创建一个 Looper,接着通过 Looper.loop()来开启消息循环,如下所示:

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

loop方法是一个死循环,唯一跳出循环的方式 MessageQueue 的 next 方法返回 null。
当 Looper 的 quit 方法被调用时,Looper 就会调用 MessageQueue 的 quit 或者 quitSafely 方法来通知消息队列退出,当消息对列被标记为退出状态是,它的 next 方法就会返回 null。

4. Handler 原理:

Handler 的工作主要包含消息的发送和接收过程。消息的发送可以通过post 的一系列方法以及 send 的一系列方法来实现,post 的一系列方法最终是通过 send 方法的一系列方法来实现的。

Handler 发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue 的 next 方法就会返回这条消息给 Looper,Looper 接收到消息后就开始处理了,最终消息由 Looper 交 Handler处理,即 Handler 的 dispatchMessage 方法就会被调用,这时 Handler 就进入了处理消息的阶段。


参考博客
作者:N0tExpectErr0r
地址:https://blog.csdn.net/qq_21556263/article/details/82759061

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