一篇讀懂Android 消息機(jī)制

是這樣的

今天凌晨三點(diǎn)??袁翁,被電話(huà)鈴聲吵醒,我勉強(qiáng)地拿起手機(jī)打開(kāi)免提:
“喂婿脸,哪位傲皇ぁ?”
“我們分手吧狐树,如果你再工作生活亂成一團(tuán)糟焙压。”抑钟,對(duì)面狠狠地說(shuō)涯曲。
“哦,是二胖霸谒幻件!”,我擦了擦冷汗心俗!
“這是她發(fā)給我的微信最后一條消息傲武,之后我的微信和手機(jī)都被拉黑了蓉驹,我現(xiàn)在是實(shí)在沒(méi)辦法了城榛,只好求助兄弟你!”
于是我給二胖分析:她需要你有一個(gè)長(zhǎng)遠(yuǎn)的目標(biāo)态兴,并能夠帶著她一起進(jìn)步狠持。

這么說(shuō)來(lái)好像Android消息機(jī)制:

  • 如果你們組成了家庭,你們家庭就相當(dāng)于一個(gè)進(jìn)程
  • 你的生活和她的生活就相當(dāng)于進(jìn)程中的兩個(gè)線程
  • 你和她分別是自己所屬生活的Handler
  • 制定一個(gè)長(zhǎng)遠(yuǎn)的目標(biāo)瞻润,目標(biāo)使你有動(dòng)力喘垂,動(dòng)力就相當(dāng)于Looper
  • 根據(jù)長(zhǎng)遠(yuǎn)目標(biāo)制定短期目標(biāo),短期目標(biāo)就相當(dāng)于Message
  • 把短期目標(biāo)規(guī)劃到日程表中绍撞,日程表就相當(dāng)于MessageQueue

“二胖正勒,我發(fā)你張圖你看看”

A168A79B-491E-4DFA-84B1-77CAF1AFF9E7.png

二胖看了一會(huì)兒聲音低沉地表示,“我沒(méi)有目標(biāo)傻铣,所以沒(méi)有Looper章贞,我的目標(biāo)從哪找呢?”

于是我接著向二胖介紹:

二胖的Looper在哪里之ThreadLocal

二胖的目標(biāo)一定與他的生活相關(guān)非洲。

我們先來(lái)看下ThreadLocal類(lèi)圖概覽

8E264975-7F25-42C2-8DDB-6B158A72B34F.png

我們發(fā)現(xiàn)Thread有一個(gè)ThreadLocalMap類(lèi)型的成員變量鸭限。ThreadLocalMap有一個(gè)保存Entry的數(shù)組。說(shuō)明:
Thread和ThreadLocalMap是一對(duì)一的關(guān)系
ThreadLocalMap和Entry是一對(duì)多的關(guān)系
ThreadLocal依賴(lài)Thread

接下來(lái)再來(lái)分析下源碼两踏,ThreadLocal是怎么getset

// 是一個(gè)泛型類(lèi)
public class ThreadLocal<T> {
    /**
     * @param t Thread
     * @return t持有的ThreadLocalMap
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    public void set(T value) {
        // Step 1 獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // Step 2 獲取當(dāng)前線程的數(shù)據(jù)集合
        ThreadLocalMap map = getMap(t);
          // Step 3 保存數(shù)據(jù)操作
        // 如果Map不為null败京,則保存數(shù)據(jù)
        if (map != null) {
            map.set(this, value);
        } else {
            // 否則創(chuàng)建Map并保存
            createMap(t, value);
        }
    }

    public T get() {
        // Step 1 獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // Step 2 獲取當(dāng)前線程下的Map
        ThreadLocalMap map = getMap(t);
        // Step 3 獲取數(shù)據(jù)操作
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 初始化Map
        return setInitialValue();
    }
}

我們發(fā)現(xiàn)在getset方法中,通過(guò)調(diào)用Thread.currentThread()來(lái)獲取當(dāng)前線程梦染,然后再獲取當(dāng)前線程的ThreadLocalMap赡麦。所以我們得出結(jié)論:

  1. ThreadLocal是以線程為界限,保存::在線程作用域范圍內(nèi)::所需要的數(shù)據(jù)。
  2. 一個(gè)ThreadLocal僅能保存一個(gè)數(shù)據(jù)
  3. ThreadLocal和Thread是多對(duì)一的關(guān)系泛粹,也就是說(shuō)车荔,一個(gè)線程可以創(chuàng)建多個(gè)ThreadLocal

通過(guò)ThreadLocal,二胖了解了自己的目標(biāo)與動(dòng)力藏在生活中戚扳。

二胖找到自己的動(dòng)力并模擬規(guī)劃日程之Looper與MessageQueue

我們先來(lái)看一個(gè)簡(jiǎn)單小例子忧便,當(dāng)在子線程定義Handler的時(shí)候,一般的寫(xiě)法是這樣的:

public class MyThread extends Thread {

    Handler mHandler;

    @Override
    public void run() {
        // Step 1 Looper做準(zhǔn)備工作
        Looper.prepare();
        // Step 2 創(chuàng)建Handler
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // 處理消息
            }
        };
          // Step 3 開(kāi)始循環(huán)
        Looper.loop();
    }
}

總共有三個(gè)步驟:那么問(wèn)題來(lái)了帽借,Looper和Handler之間是什么關(guān)系呢珠增?我們先來(lái)看下類(lèi)圖:

DA04F63F-3B23-4C3A-80BA-BD599629EDF8.png

通過(guò)類(lèi)圖我們發(fā)現(xiàn):
Looper與Thread、MessageQueue的關(guān)系

  1. Looper類(lèi)定義了一個(gè)靜態(tài)的ThreadLocal砍艾,用來(lái)存儲(chǔ)Looper對(duì)象
  2. 一個(gè)Looper持有一個(gè)Thread的引用
  3. 一個(gè)Looper持有一個(gè)MessageQueue的引用

Handler與Looper蒂教、MessageQueue的關(guān)系

  1. 一個(gè)Handler持有一個(gè)Looper的引用
  2. 一個(gè)Handler持有一個(gè)MessageQueue的引用

但這只是通過(guò)類(lèi)圖看出來(lái)的,還沒(méi)有和代碼整合到一起脆荷,我們來(lái)通過(guò)源碼分析

二胖找到自己的Looper并買(mǎi)了一本日程表之Looper.prepare()

public final class Looper {
    // 用于保存Looper的ThreadLocal凝垛,靜態(tài)。
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

    private static void prepare(boolean quitAllowed) {
        // Step 1 如果ThreadLocal存在Looper蜓谋,則拋出異常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
          // Step 2 創(chuàng)建一個(gè)looper保存到ThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }

    private Looper(boolean quitAllowed) {
          // Step 1 創(chuàng)建一個(gè)消息隊(duì)列
        mQueue = new MessageQueue(quitAllowed);
          // Step 2 獲得當(dāng)前線程
        mThread = Thread.currentThread();
    }
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
}

Looper定義了一個(gè)靜態(tài)ThreadLocal梦皮,Looper.prepare()會(huì)先判斷sThreadLocal里是否已經(jīng)存在Looper,如果存在則拋出異常桃焕。如果不存在則創(chuàng)建一個(gè)Looper保存到ThreadLocal里剑肯。這可以表明:ThreadLooper是一對(duì)一的關(guān)系,在創(chuàng)建Looper的時(shí)候观堂,Looper知道自己是屬于MyThread線程的Looper让网,會(huì)同時(shí)創(chuàng)建一個(gè)MessageQueue,所以Thread师痕、Looper溃睹、MessageQueue他們之間都是一對(duì)一的關(guān)系。

二胖找到自己的Looper并買(mǎi)了一本日程表之new Handler()

public class Handler {
    ...
    final Looper mLooper;
    final MessageQueue mQueue;

    public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
        ...
          // 1.初始化Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
          // 2.初始化MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    ...
}

由于已經(jīng)執(zhí)行了Looper.prepare()胰坟,此時(shí)Looper已經(jīng)創(chuàng)建并保存在sThreadLocal里因篇,當(dāng)new Handler()時(shí),Handler知道了已經(jīng)創(chuàng)建的Looper在哪里腕铸,通過(guò)Looper找到已經(jīng)創(chuàng)建了的MessageQueue惜犀。

  1. HandlerLooper是多對(duì)一的關(guān)系
  2. HandlerMessageQueue是多對(duì)一的關(guān)系,即一個(gè)線程可以創(chuàng)建多個(gè)Handler
  3. Handler的創(chuàng)建必須在調(diào)用Looper.prepare()之后狠裹,否則會(huì)拋出異常

二胖鼓足了動(dòng)力之Looper.loop()

public final class Looper {
    
    public static void loop() {
        // Step 1.獲得當(dāng)前線程的Looper
        final Looper me = myLooper();
        // Looper為空拋出異常虽界,必須先調(diào)用Looper.prepare()來(lái)初始化Looper
        if (me == null) {
            throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
        }
        // Step 2.獲得Looper持有的消息隊(duì)列
        final MessageQueue queue = me.mQueue;
        ...
        // Step 3.開(kāi)啟無(wú)限循環(huán)
        for (;;) {
            ...
            // Step 4.從隊(duì)列中取出一條消息,可能阻塞
            Message msg = queue.next(); // might block
            // msg為null涛菠,消息隊(duì)列已經(jīng)停止了
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                // Step 5.派送給msg的目標(biāo)Handler去處理消息
                msg.target.dispatchMessage(msg);

            } finally {
                ...
            }
            // Step 6.回收消息
            msg.recycleUnchecked();
        }
    }
}

當(dāng)調(diào)用Looper.loop的時(shí)候莉御,首先獲得屬于當(dāng)前線程的LooperMessageQueue撇吞,并開(kāi)啟無(wú)限循環(huán),不斷地MessageQueue中取出消息礁叔,如果能夠取出消息則發(fā)送給這條消息的所屬Handler去處理牍颈,最后回收消息。如果取不出消息琅关,則當(dāng)前線程會(huì)阻塞在Step 4煮岁,直到能夠取出一條消息,再進(jìn)行之后的步驟涣易。

那么當(dāng)調(diào)用queue.next()的時(shí)候是怎么從MessageQueue中取出一條消息的呢画机?

MessageQueue出隊(duì)操作

public final class MessageQueue {
     
    // 消息隊(duì)列的隊(duì)首
    Message mMessages;   
     
    Message next() {
    ...
        // 下一條消息出隊(duì)時(shí)間
        int nextPollTimeoutMillis = 0;
        // 無(wú)限循環(huán)
        for (;;) {
            ...
            // Step 0 等待nextPollTimeoutMillis長(zhǎng)的時(shí)間,喚醒線程
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // Step 1 當(dāng)前隊(duì)頭msg
                Message msg = mMessages;
                // 存在msg新症,但是這個(gè)msg不知道處理它的事務(wù)的目標(biāo)Handler是誰(shuí)
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        // 取下一個(gè)Message
                        msg = msg.next;

                    } while (msg != null && !msg.isAsynchronous());
                }
                
                if (msg != null) {
                    // Step 2 如果msg還沒(méi)有到要處理的時(shí)間(延時(shí)的消息)步氏,則獲取距離處理這條msg的時(shí)間差,
                    // 待下次循環(huán)調(diào)用nativePollOnce時(shí)徒爹,當(dāng)前線程處于等待狀態(tài)荚醒,直到要處理msg的時(shí)間時(shí)喚醒線程
                    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 {
                          // Step 3 獲取消息
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            // 隊(duì)首msg出隊(duì)列,直接后繼走到隊(duì)首的位置
                            mMessages = msg.next;
                        }
                        // 解除出隊(duì)的msg和其直接后繼的關(guān)聯(lián)
                        msg.next = null;
                        // 標(biāo)記這個(gè)msg已經(jīng)被使用了
                        msg.markInUse();
                        // 返回這個(gè)出隊(duì)的msg
                        return msg;
                    }
                }
                  ...
            }
        }
    }
}

MessageQueue持有對(duì)隊(duì)首Message的引用隆嗅,那么當(dāng)Looper.loop()中調(diào)用next()時(shí)界阁,(Step 1)首先獲取當(dāng)前隊(duì)列的第一條消息,然后判斷是否到達(dá)處理這條消息的時(shí)間榛瓮,(Step 2)如果沒(méi)有當(dāng)前還沒(méi)有到達(dá)要處理這條消息的時(shí)機(jī)铺董,則計(jì)算出當(dāng)前時(shí)間距離這條消息處理時(shí)間的時(shí)間差nextPollTimeoutMillis,待下次循環(huán)時(shí)禀晓,(Step 0)線程將等待nextPollTimeoutMillis長(zhǎng)時(shí)間再做出隊(duì)操作。如果當(dāng)前已經(jīng)到達(dá)要處理這條消息的時(shí)間坝锰,則出隊(duì)msg并返回粹懒。所以Looperloop()中就拿到了當(dāng)前出隊(duì)的消息。

從以上邏輯我們發(fā)現(xiàn)顷级,next()操作時(shí)從隊(duì)首消息凫乖,next,next依次訪問(wèn)的弓颈,如果當(dāng)前消息還未到處理時(shí)間帽芽,則會(huì)等待,并不會(huì)訪問(wèn)下一條消息翔冀。

那么假設(shè)隊(duì)列中有A导街、B、C三條消息纤子,當(dāng)訪問(wèn)A時(shí)搬瑰,發(fā)現(xiàn)此刻還未到處理A消息的時(shí)間款票,但已經(jīng)到處理B消息的時(shí)間,線程卻處于等待狀態(tài)泽论。如果是這樣的話(huà)太不科學(xué)艾少,由此我們猜測(cè),消息隊(duì)列是按照消息處理時(shí)間緊急程度來(lái)排列的翼悴,并不是按照先進(jìn)先出排列的缚够。那么我們來(lái)驗(yàn)證一下吧!

二胖開(kāi)始短期目標(biāo)日程規(guī)劃之MessageQueue#enqueueMessage

當(dāng)我們?cè)谄渌€程調(diào)用mHandler.sendMessage(msg)MyThread發(fā)送一條消息時(shí)鹦赎,在多層調(diào)用后調(diào)用HandlerenqueueMessage


public class Handler {

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
          // 1.target在此處初始化
          msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
          // 2.調(diào)用MessageQueue的入隊(duì)列操作
        return queue.enqueueMessage(msg, uptimeMillis);
    }
}

然后再調(diào)用MessageQueueenqueueMessage進(jìn)行入隊(duì)操作

public final class MessageQueue {
    
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        synchronized (this) {
            ...
            msg.markInUse();
            msg.when = when;
            // Step 1 指向隊(duì)首的引用
            Message p = mMessages;
            boolean needWake;
            // Step 2 沒(méi)有隊(duì)首msg 或者 when == 0 表示不延時(shí)潮瓶,立即發(fā)送(所以入隊(duì)首)
            // 或者入隊(duì)msg的延時(shí)小于隊(duì)首的延時(shí)(比隊(duì)首先發(fā)送,所以插入隊(duì)首)
            if (p == null || when == 0 || when < p.when) {
                
                // 把msg放置在隊(duì)首
                msg.next = p;
                // 更新對(duì)隊(duì)首的引用為msg
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // Step 3 如果隊(duì)列存在 并且 加入的msg延時(shí)大于隊(duì)首的延時(shí)钙姊,則遍歷隊(duì)列毯辅,插入到適當(dāng)?shù)奈恢?
                for (;;) {
                    // 臨時(shí)變量指向P
                    prev = p;
                    // 后繼
                    p = p.next;
                    // 不存在后繼 或者 新插入的msg延時(shí)小于 后繼的延時(shí)
                    if (p == null || when < p.when) {
                        // 跳出循環(huán)
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 把msg插入到prev和p之間變成 prev -> msg -> p
                msg.next = p;
                prev.next = msg;
            }
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
}

通過(guò)分析源碼我們知道有兩種情況

判斷是否插入隊(duì)首

  • 不存在隊(duì)首,即隊(duì)列不存在
  • 存在隊(duì)首但是當(dāng)前入隊(duì)msg是立刻要發(fā)送的
  • 存在隊(duì)首但是當(dāng)前入隊(duì)msg延時(shí)時(shí)間小于隊(duì)首消息延時(shí)時(shí)間
    以上三種情況是或者的關(guān)系煞额,只要存在其中一種情況思恐,就把當(dāng)前入隊(duì)消息插入隊(duì)首

遍歷插入到合適的位置

  • 存在隊(duì)首
  • 要入隊(duì)的消息延時(shí)大于隊(duì)首的延時(shí)
    以上兩種情況是且的關(guān)系,循環(huán)遍歷隊(duì)列根據(jù)延時(shí)進(jìn)行插入操作膊毁。

我們發(fā)現(xiàn)胀莹,只要通過(guò)enqueueMessage來(lái)入隊(duì)的消息,msg.target一定不為空婚温,否則會(huì)拋出異常描焰。

結(jié)語(yǔ)

自從二胖學(xué)會(huì)了Android消息機(jī)制走上人生巔峰后,他們夫妻二人愈發(fā)恩愛(ài)了栅螟。
這是一個(gè)深夜荆秦,桌子邊的喜帖在燈光下閃閃發(fā)光,敲著代碼的我有些黯然神傷力图!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末步绸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吃媒,更是在濱河造成了極大的恐慌瓤介,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赘那,死亡現(xiàn)場(chǎng)離奇詭異刑桑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)募舟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)祠斧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人胃珍,你說(shuō)我怎么就攤上這事梁肿◎涯埃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵吩蔑,是天一觀的道長(zhǎng)钮热。 經(jīng)常有香客問(wèn)我,道長(zhǎng)烛芬,這世上最難降的妖魔是什么隧期? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮赘娄,結(jié)果婚禮上仆潮,老公的妹妹穿的比我還像新娘。我一直安慰自己遣臼,他們只是感情好性置,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著揍堰,像睡著了一般鹏浅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屏歹,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天隐砸,我揣著相機(jī)與錄音,去河邊找鬼蝙眶。 笑死季希,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的幽纷。 我是一名探鬼主播式塌,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼霹崎!你這毒婦竟也來(lái)了珊搀?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尾菇,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后囚枪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體派诬,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年链沼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了默赂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡括勺,死狀恐怖缆八,靈堂內(nèi)的尸體忽然破棺而出曲掰,到底是詐尸還是另有隱情,我是刑警寧澤奈辰,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布栏妖,位于F島的核電站,受9級(jí)特大地震影響奖恰,放射性物質(zhì)發(fā)生泄漏吊趾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一瑟啃、第九天 我趴在偏房一處隱蔽的房頂上張望论泛。 院中可真熱鬧,春花似錦蛹屿、人聲如沸屁奏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坟瓢。三九已至,卻和暖如春湿颅,著一層夾襖步出監(jiān)牢的瞬間载绿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工油航, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崭庸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓谊囚,卻偏偏與公主長(zhǎng)得像怕享,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镰踏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

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