源码解读 | Handler 消息机制

1. 概述

Android 的消息机制其实就是指 Handler 机制,在阅读源码中发现 Handler 的使用非常频繁,主要作用是实现线程的切换。

Android 系统里 UI 控件的设计并不是线程安全的,当我们在子线程中完成耗时操作需要访问 UI 时,如果出现并发的情况(即多个子线程同时访问同一个View)就会出现不可预期的情况,这时候就要用到 Handler 来处理子线程对 UI的访问控制。

这里解释了我们为什么不能在子线程访问 UI,因为 Android 系统的 UI 控件不是线程安全的。Android 为了保证 UI 的线程安全问题,采用了单线程模型来处理 UI,所以 Handler 的作用是实现从子线程到 UI 线程的切换。这是大部分对 Handler 的定义,了解 Handler 的消息机制之后可以知道,其实 Handler 所谓的切换线程只是在创建 Handler 的线程中做好接收并处理消息的准备,然后在子线程中完成耗时任务后发送消息,看起来好像是在切换线程,实际上是实现消息的发送和接收功能。

2. Handler 的常见使用方法

    private MyHandler mHandler = new MyHandler();
    protected void onCreate(Bundle savedInstanceState) {
        ...
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //step2:创建子线程1
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //step3:创建消息(Message)
                        Message msg = new Message();
                        msg.what = 1;
                        msg.obj = "延时2s,通过sendMessage" + "\n"
                                + "我是工作线程1的消息";
                        //step4:发送消息(Message)到消息队列(MessageQueue)
                        mHandler.sendMessage(msg);
                    }
                }).start();//step5:开启子线程1
            }
        });

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //创建子线程2
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                //更新UI
                                msgText.setText("延时5s,通过post" + " \n" +
                                        "我是工作线程2传递的消息");
                            }
                        });
                    }
                }).start();//开启子线程2
            }
        });
    }

    private static class MyHandler extends Handler {
        public MyHandler() {
        }
        //复写handleMessage实现UI更新
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //更新UI,msgText需用static修饰
            msgText.setText((CharSequence) msg.obj);
        }
    }

上述代码,定义两个 Button 分别用于创建两个子线程,子线程1延时2s,调用 Handler.sendMessage()发送消息,子线程2延时5s,调用 Handler.post() 方式延时消息。结尾处,定义了一个 Handler 的静态内部类,实现了方法 handleMessage() 用于接收来自子线程的消息并更新控件 msgText 的内容。

3. Handler 的工作原理

3.1 创建 Handler
3.1.1 调用构造方法创建
    private Handler handler = new Handler();

来看 Handler 的构造方法:

    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 " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

先是判断 FIND_POTENTIAL_LEAKS,用于提示可能存在内存泄漏的异常,接着判断 Looper 是否为空,为空抛出异常,最后就是对参数的初始化了。这里可以知道我们创建 Handler 时一定要在有 Looper 的线程中创建,否则会报错。

3.1.2 Handler 子类创建(静态内部类)

继承自 Hanlder 的子类一定要实现方法 handleMessage() 用于接收消息并更新 UI。

    private MyHandler handler = new MyHandler();
    private static class MyHandler extends Handler {
        //复写handleMessage实现UI更新
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //更新UI
        }
    }
3.2 Handler 发送消息

用 Handler 发送消息可以使用 sendMessage(Message msg)、post(Runnable r)、postDelay(Runnable r, long delayMillis),不同的是 sendMessage() 传递的是 Message 参数并将 Message 加入到 MessageQueue中,post()和postDelay() 传递的是 Runnable 参数将其存在 msg.callback 中后再将该消息加入到 MessageQueue 中,最终这三个方法都会调用 sendMessageAtTime() 方法。

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) { //MessageQueue不能为空,否则异常
            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) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

sendMessageAtTime() 方法先判断消息队列 mQueue 是否为空,不为空则调用 enqueueMessage() 方法,enqueueMessage() 里将当前 Handler 放在 msg.target 中,然后判断是否是异步,是则调用 msg.setAsynchronous(ture) 设置异步标志,最后调用了 MessageQueue.enqueueMessage() 将该消息 Message 加入队列 MessageQueue中。基本上这一过程就是实现将我们在子线程中需要发送的消息加入消息队列中等待处理,而消息如何入队,消息如何被发送到 UI 线程等问题就要进入到 MessageQueue 的中去探究了。

3.3 Handler 接收和处理消息

先看 dispatchMessage(),用于分发我们的消息。

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这里 msg.callback 返回的是 Runnable 对象,当我们使用 post() 或者 postDelay() 发送消息时,msg.callback 就是我们在这两个方法里面传入的 Runnable。从代码中看出,如果使用的是 post() 或者 postDelay() 发送消息,那么调用 handleCallback(msg) 处理消息,如果调用的是 sendMessage() 发送消息还要分两种情况:
1) mCallback 不为空时(这里的 mCallback 是 Handler 内部接口类 Callback 的一个对象),调用接口 Callback 的接口方法 handleMessage() 处理消息;
2) mCallback 为空,调用 handleMessage() 处理消息。

具体如何处理消息呢?依然是上面三种情况,需要分情况了解:

1) handleCallback(),来自 post()和 postDelay() 的消息在 handleCallback() 方法中处理;

    private static void handleCallback(Message message) {
        message.callback.run();
    }

handleCallback() 里调用了 message.callback.run(),我们已经知道 message.callback 是我们传入的 Runnable,调用 Runnable 的 run() 方法,而 run() 很明显就是在我们传入 Runnable 对象时去实现的 run() 方法,所以使用 post() 和 postDelay() 发送消息后消息的接收和处理操作就是在发送 Runnable 参数对象的 run() 方法中去实现的了。具体操作怎么是什么就是我们在创建 Handler 的线程中自己去完成了。

2) mCallback.handleMessage(msg),mCallback 不为空时调用,不为空的情况是在创建 Handler 是传入了 Callback 对象。

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

mCallback 是一个接口类对象,我们在这里调用了 mCallback.handleMessage(msg),实现的是把当前的消息传给 handleMessage(),具体的接收消息和处理消息的操作就要在 handleMessage() 中去实现,即在我们创建 Handler 的线程中去实现该方法。

3) handleMessage(),mCallback 为空时调用

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

handleMessage() 是 Handler 中的一个空方法,从注释中就知道,当我们以 Handler 子类的方式创建 Handler 时就一定要实现该方法,同样该方法的作用也是用于接收和处理消息的,具体的就需要我们在创建 Handler 子类的线程中去实现。

上面的描述 dispatchMessage() 分发事件的流程图如下:

Handler消息分发流程.png

4. MessageQueue 的工作原理

消息队列

4.1 消息入队

前面在分析 Handler 发送消息时会调用 MessageQueue 的 enqueueMessage() 方法实现消息的入队。

    boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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;
            }
            ...
            if (needWake) {
                nativeWake(mPtr); //唤醒阻塞的线程
            }
        }
        return true;
    }

Message 是一个链表结构,这里的 mMessages 就是一个 Message,消息入队实际上是实现的链表的插入工作,即向 mMessages 中插入一个 Message。
1)消息不延时或者 mMessages 为空或者当前消息延时时长小于 mMessage 的延时时长,将消息作为链表的第一个元素插入到 mMessages 中;
2)否则进入循环,遍历 mMessages 直到该消息链表中只有一条消息,即消息队列中的消息都比当前消息先执行或者当前消息的延时小于 mMessages 中某一条消息的延时(即需要执行当前消息了),跳出循环,然后将当前消息插入到 mMessages 中,mMessages 中若是只剩一条消息,则插入到链表尾部,否则插入到消息延时较大的那条消息的前面;
3)线程正在阻塞中且有新消息到来(新消息到来是指消息需要立即执行,不包含延时消息),needWake 为ture,调用 nativeWake() 唤起阻塞的线程

4.2 消息出队
    Message next() {
        ...
        for (;;) {
            ...
            //阻塞线程,阻塞时长为 netPollTimeoutMillis
            nativePollOnce(ptr, nextPollTimeoutMillis); //1)
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    do { //2)
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) { //3)
                        // 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 { //删除消息
                        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; //返回消息并跳出死循环
                }
              ...
    }

next() 方法里面是一个死循环,如果消息队列中没有消息,那么 next() 方法就一直阻塞在那里,当有新消息到来时,next() 返回这条消息并将其从单链表 mMessages 中移除。
1)调用 nativePollOnce(ptr, nextPollTimeoutMillis) 阻塞线程,阻塞时长为 netPollTimeoutMillis,该方法将一直阻塞线程直到一条需要立即执行的消息到来,如果消息有延时仍会阻塞

nativePollOnce 和 nativeWake 的核心魔术发生在 native 代码中,native MessageQueue 利用名为 epoll 的 Linux 系统调用。其实 nativePollOnce() 大致等同于 Object.wait(), nativeWake() 等同于 Object.notify(),但它们的实现完全不同:nativePollOnce() 使用 epoll ,而 Object.wait() 使用 futex

2)msg 不为空且 msg.target 为空,遍历消息链表,找到一条 同步消息 赋值给 msg;
3)msg 不为空,如果还没到msg的执行时间,更新 nextPollTimeoutMillis,继续阻塞线程 重复上述步骤,直到到达msg执行时间,再将消息msg从消息队列中移除并返回给 Looper(next() 方法在 Looper.loop() 中调用);

  • Handler 如何实现消息延时的?
    Handler 实现消息的延时是在 MessageQueue 中实现的,通过分析了消息的入队和出队操作,很明显能够知道,如果消息不是延时消息,调用 nativeWake() 唤醒消息,如果是延时消息,首先在入队时会按照消息的延时时长在消息队列中排序,较早执行的消息排在消息队列的前面;接着由于在 Looper.loop() 中调用了next() 方法轮询消息队列,next() 首先通过 nativePollOnce() 阻塞线程,当消息队列中有消息需要立即执行则返回给 Looper.loop(),如果消息是延时消息,继续阻塞直到消息达到延时时长需要立即执行;
4.3 退出消息队列
  • quit()
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

1)根据构造方法传来的参数 mQuitAllowed 判断当前消息队列是否可退出,如果是在主线程创建 Looper 时创建的是不允许退出的,在子线程创建 Looper 时创建可以退出;
2)安全退出调用 removeAllFutureMessagesLocked() 方法,不安全退出调用 removeAllMessagesLocked() 方法。

  • removeAllMessagesLocked()
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

这里面的实现很简单,就是遍历我们的 mMessages 链表,回收每一个消息,回收时接收到的所有待处理的消息都会被回收不再处理。

  • removeAllFutureMessagesLocked()
    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {//消息是延时消息,不处理直接退出
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) { //死循环,等待需要处理的消息被处理
                    n = p.next;
                    if (n == null) { //消息为空则跳出循环
                        return;
                    }
                    if (n.when > now) { //是延时消息跳出循环
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

1)mMessages 链表中还有消息没有处理但是是延时信息(即需要在未来某个时刻处理),不再处理,直接调用 removeAllMessagesLocked() 回收掉延时消息;
2)进入死循环,遍历消息链表等待消息被处理;
3)结束死循环条件:1. 消息链表为空;2. 消息链表遍历到的消息是延时消息(消息链表在插入消息时是按照消息执行时间先后排序插入的,如果遍历到延时消息,那么该消息之后的消息都是延时消息);
3)回收消息链表中的消息;

quit() 和 quitSafely() 相同与不同
  • 不同:quit() 直接回收掉消息链表中的所有消息;quitSafely() 不再处理延时消息但会把非延时的消息通过 Looper 发送给 Handler 处理;
  • 相同:Looper调用了 qiut()或quitSafely() 之后都不会再接收 Handler 发送的消息了。

5. Looper 工作原理

5.1 Looper.prepare()

Looper.prepare() 用于在子线程中创建 Handler 时调用,子线程中创建 Handler 如果子线程没有 Looper 必须要调用 Looper.prepare() 为子线程创建 Looper。

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

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

1)子线程创建 Handler 时创建的 Looper 是可以退出的,因为 sThreadLocal.set(new Looper(quitAllowed)) 参数 quitAllowed 为 true,由构造方法可知 quitAllowed 参数在这里通过 MessageQueue 唯一的构造方法 MessageQueue(boolean quitAllowed) 传给了 MessageQueue;
2)子线程创建 Handler 时,一个线程只能创建一个 Looper,Looper 通过 ThreadLocal 存储子线程的 Looper,ThreadLocal 内部机制保证了线程 Looper 的唯一性。

5.2 Looper.prepareMainLooper()

Looper.prepareMainLooper() 在主线程中调用,是在启动应用时系统创建应用的主线程(UI 线程)时系统自己去调用的,不需要我们在代码中去手动调用。

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

1)调用prepare(false),参数 quitAllowed 为 false,表明主线程(UI线程)的 Looper 是不允许退出的;
2)sMainLooper = myLooper() 保存主线程的 Looper。

5.3 Looper.loop()

在当前线程中运行消息队列,在调用了 Looper.loop() 之后通过调用 Looper.quit() 结束循环。

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) { //判断当前线程是否含有 Looper
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) { //消息为空时跳出循环
                return;
            }

            ...
            try {//调用 Handler 的分发事件方法
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked(); //回收消息
        }
    }

1) 判断当前调用 Looper.loop() 的线程是否有 Looper,提示我们在线程中调用 Looper.loop() 之前要调用 Looper.prepare() 为当前线程创建一个 Looper,否则在没有 Looper 的线程中调用 Looper.loop() 会失败并抛出异常;
2)进入死循环,调用 MessageQueue 的 next() 方法,循环中遍历消息队列,如果消息不为空执行 msg.target.dispatchMessage(msg) 分发消息,msg.target 即是我们的 Handler 对象,这里就进入到了 Handler 的 dispatchMessage() 方法中去分发当前的消息,位于主线程中的 handleMessage()run()Callback.handleMessage() 根据 Handler 发送消息的方式分情况接收消息并处理(更新UI等操作);
3)调用 msg.recycleUnchecked() 回收消息,这条消息可能正在使用;
4)结束死循环条件:如果遍历到的消息为空,跳出循环。

5.4 Looper 退出

Looper 提供手动退出的方法,quit() 和 quitSafely(),用于停止 Looper.loop() 中的循环。当子线程中没有消息需要处理的时候,我们需要手动去调用 quit()/quitSafely() 结束消息循环,否则子线程会一直存在且处于等待状态,Looper 一旦退出,子线程也就结束了。

    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

quit() 和 quitSafely() 方法都调用了 MessageQueue 的 quit() 方法,唯一不同在于传入的参数不同,参数表示是否安全退出,具体实现请看前面对 MessageQueue 的 quit() 方法的分析。

6. Handler 消息机制流程

以使用 Handler 的 sendMessage() 为例 Handler 消息机制的具体流程如下:

  • 创建 Looper 开始轮询
    → Looper.prepare() → ThreadLocal.set() → new MessageQueue()
    → Loopre.loop()
    → MessageQueue.next() //消息出队
    → Handler.dispatchMessage() //分发消息
    → Looper.quit() / Looper.quitSafely()
    → MessageQueue.quit()

  • 消息入队
    → mHandler.sendMessage()
    → Handler.sendMessage()
    → Handler.sendMessageDelay()
    → Handler.sendMessageAtTime()
    → Handler.enqueueMessage()
    → MessageQueue.enqueueMessage() //消息入队

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