Android異步消息處理機(jī)制02 —— HandlerThread 篇

還記得上篇文章的傻傻子線程嗎?

回顧一下党觅。當(dāng)時(shí),這條子線程經(jīng)過一段時(shí)間的網(wǎng)絡(luò)請(qǐng)求斋泄,終于得到了數(shù)據(jù)杯瞻,想直接在 UI 上顯示數(shù)據(jù),卻被告知只有在主線程才能修改 UI 數(shù)據(jù)炫掐。為了將數(shù)據(jù)傳遞給主線程魁莉,子線程向主線程設(shè)置的一個(gè)叫 Handler 的消息處理者,發(fā)送了自己的請(qǐng)求 Message,并攜帶上了自己的數(shù)據(jù)旗唁。接著畦浓,Message 在 Handler 內(nèi)部先排列在 MessageQueue 里,再經(jīng)過主線程為 Handler 準(zhǔn)備好的 Looper检疫,通過人家的 loop() 方法讶请,才一個(gè)個(gè)被送給 Handler 進(jìn)行處理,在處理方法中屎媳,結(jié)果才得以顯示在 UI 上秽梅。

被這套機(jī)制吸引的子線程異想天開,想效仿主線程剿牺,給自己也設(shè)置個(gè) Handler企垦,結(jié)果吃了苦頭,發(fā)現(xiàn)沒準(zhǔn)備 Looper 的話晒来,Handler 壓根設(shè)置不了钞诡,接著又發(fā)現(xiàn),Looper湃崩、Handler 設(shè)置好了還不行荧降,這 Looper 還得開啟 loop(),消息處理才能真正流轉(zhuǎn)起來攒读。然而人家主線程朵诫,應(yīng)用啟動(dòng)時(shí)就在 ActivityThread 的 main() 方法里為它全權(quán)安排好了 Looper 事宜,自己就放心 new Handler() 即可薄扁,真是線程比線程剪返,氣死個(gè)線程。

然而很多人覺得邓梅,給子線程這么多磕磕絆絆是有道理的脱盲。人家主線程設(shè)置 Handler 順順利利,因?yàn)橛衅渌€程要托它處理消息日缨,而且主線程 Handler 處理完的結(jié)果可以直接更新在 UI 上钱反,你子線程就算設(shè)置了屬于自己的 Handler,別的線程找你又能干些什么呢匣距?難不成你也能把處理結(jié)果往 UI 上放面哥?

子線程里設(shè)置的 handler,能不能在 handleMessage(msg) 方法里訪問UI毅待?

當(dāng)然不行尚卫!想都不用想哦,常言道恩静,只有在主線程才可以更改 UI焕毫,子線程不可以蹲坷。人家主線程 handler 就是在主線程創(chuàng)建的,而子線程 handler 則是在子線程里創(chuàng)建的邑飒,要是在子線程 handler 的 handleMessage(msg) 方法里訪問 UI循签,一準(zhǔn)報(bào)錯(cuò),異常信息都可以提前告訴你:

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

不信我們驗(yàn)證一下:

public class MainActivity extends AppCompatActivity{

    private Handler mUiHandler;
    private Handler mZiHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        
        mUiHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 200:
                        mTextView.setText((String) msg.obj);
                        break;
                }
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 1
                Looper.prepare();
                // 2
                mZiHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case 100:
                                mTextView.setText((String) msg.obj);
                                break;
        
                        }
                    }
                };
                // 3
                Looper.loop();
        
                Message message = Message.obtain();
                message.what = 100;
                message.obj = "請(qǐng)子線程處理者幫我在界面顯示這句話";
                mZiHandler.sendMessage(message);
            }
        }).start();     
}

代碼很明朗疙咸,首先县匠,聲明了兩個(gè) Handler,一個(gè)主線程的撒轮,一個(gè)子線程的乞旦,然后,在主線程里初始化 mUiHandler题山,方式和以前一樣兰粉,直接 new;接著開啟一條子線程顶瞳,在其內(nèi)部 run() 方法里玖姑,初始化 mZiHandler。嗯慨菱,注意看看子線程里建 handler 的操作焰络,代碼里的寫法確實(shí)是標(biāo)準(zhǔn)的三步走,一步?jīng)]落哦符喝。只不過最后闪彼,我們讓子線程的 Message 作個(gè)死,棄主線程處理者 mUiHandler 于不顧协饲,轉(zhuǎn)投自己的子線程處理者 mZiHandler 畏腕,向它表達(dá)自己想要更新 UI 的意愿。能成功嗎囱稽?我們拭目以待:

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

意料之中的勝利~ 畢竟郊尝,常識(shí)怎么可能出錯(cuò)呢?

不過接下來的操作战惊,列位可瞧好了,我們把子線程內(nèi)部改成如下幾句

new Thread(new Runnable() {
    @Override
    public void run() {
        // 1
        Looper looper = Looper.getMainLooper();
        // 2
        mZiHandler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 100:
                        mTextView.setText((String) msg.obj);
                        break;

                }
            }
        };

        Message message = Message.obtain();
        message.what = 100;
        message.obj = "請(qǐng)子線程處理者幫我在界面顯示這句話";
        mZiHandler.sendMessage(message);
    }
}).start();

和之前的三步走相比扎即,每步都不一樣不說吞获,居然還少了一步,肯定沒戲谚鄙,運(yùn)行一下看看各拷。

……場面一度十分尷尬,子線程的數(shù)據(jù)闷营,居然在 UI 上顯示了烤黍!

趕緊看看哪里出了問題知市,沒多少,全在這三步

1. 這次沒為子線程新準(zhǔn)備 looper速蕊,而是直接拿了主線程的 MainLooper
2. 新建 handler 時(shí)將獲取的 MainLooper 作為參數(shù)傳進(jìn)了 Handler 構(gòu)造函數(shù)
3. 并沒有 loop() 起來

這時(shí)嫂丙,mUiHandler 最覺無顏以對(duì),原本以為它生于主線程规哲,作用于主線程跟啤,所以 UI 操作才不在話下信手拈來,沒想到唉锌,卻被生于子線程的 mZiHandler 砸了場子隅肥。

目前可以看出,一個(gè)線程的 Handler袄简,處理方法能否作用于 UI腥放,并不取決于 handler 歸屬在哪,而是要看構(gòu)建它消息處理機(jī)制的 looper 是屬于哪個(gè)線程的绿语。

之前子線程 handler 的失敗秃症,在于新建時(shí),new Handler() 操作會(huì)默認(rèn)取得當(dāng)前線程的 looper汞舱,進(jìn)而構(gòu)建消息處理機(jī)制伍纫,looper屬于子線程,MessageQueue 也屬于子線程昂芜,handler 接收的 message 就在子線程中排列莹规、遍歷,handleMessage(msg) 所屬的 dispatchMessage(msg) 方法泌神,也還嵌套在 loop() 方法里良漱,整個(gè)處理過程自然也就跳不出子線程的范圍,當(dāng)然不能去碰更新 UI 這雷區(qū)欢际。

而后來的成功也是一樣的道理母市,looper 采用的是 MainLooper,消息等于說都跑主線程里去了损趋,處理時(shí)不僅可以放心大膽地更新 UI患久,MainLooper 的 loop() 方法也不用操心,畢竟應(yīng)用啟動(dòng)時(shí)就安排好了浑槽。

嗯蒋失,真是有意義的一次探索啊……

繞了一大圈,說回異想天開的子線程桐玻,關(guān)于更新 UI 還是不要想太多篙挽,不是說你不行,而是與其給你 MainLooper 去構(gòu)建消息處理機(jī)制镊靴,不如直接給主線程的 handler 處理好了铣卡,反正最終都是在主線程流轉(zhuǎn)處理链韭,沒必要你還建個(gè) handler 走形式。

沉默了半天的子線程終于開口了煮落。

子線程:其實(shí)……我要建 handler敞峭,根本就沒打算作更新UI用……

我:?州邢!

子線程:前面你根本沒給我說話機(jī)會(huì)儡陨,絮絮叨叨就把我不適合去更新 UI 的事剖析完了,只不過誰說建 handler 就是要去更新UI了量淌?異步消息處理機(jī)制在你眼里就這么狹隘嗎骗村?

我:不更新 UI,那別的子線程找你的 handler 干嘛呀枢?有什么訴求是你能干的而人家主線程干不了的嗎胚股?

子線程:沒有。

我:……

子線程:但是裙秋,有主線程不方便做的琅拌,或者說,它需要幫忙的摘刑。

我:展開講講进宝?

子線程:之前的例子都太簡單了,一個(gè)子線程枷恕,發(fā)一個(gè)簡單的消息党晋,通知主線程簡單地顯示,根本沒優(yōu)化空間……你想一下這種的情況徐块,假如未玻,咱寫的是省高考成績處理程序……

我:嚯,不得了

子線程:嗯胡控,這程序里扳剿,開了100條子線程,它們都忙于查詢考生的分?jǐn)?shù)信息昼激,全省20萬考生的分?jǐn)?shù)信息庇绽,包含語數(shù)外、數(shù)理化橙困、政史地以及學(xué)科權(quán)重敛劝,加分情況什么的。因?yàn)椴樵儥z索工作繁重麻煩纷宇,所以這些子線程就只專心做查詢這一件事,查完一個(gè)考生蛾方,就把其分?jǐn)?shù)結(jié)果以 JSON 的形式發(fā)給主線程像捶。

我:它們要主線程干嘛上陕?不還是得 UI 顯示~

子線程:是要顯示,不過要求顯示的拓春,是一個(gè)綜合分排行榜释簿。

我:綜合分排行榜?你的意思是硼莽,主線程 handler 收到一個(gè)線程的 message庶溶,從里面拿出個(gè) JSON,自己解析懂鸵、計(jì)算出一個(gè)考生的綜合分偏螺,滿20萬個(gè)了再去排序,這么繞一大圈最后再顯示匆光?

子線程:嗯套像,其實(shí)可以邊來邊排…先不管這些,對(duì)终息,反正就是這樣夺巩,你說這種情況是不是很不合理!

我:不合理周崭!主線程本來就要避免耗時(shí)的操作柳譬,20萬個(gè)數(shù)的排序我看都費(fèi)勁,還解析計(jì)算续镇,這步驟各子線程自己就不能擔(dān)著點(diǎn)美澳?

子線程:前面說了啊,子線程們查詢檢索就夠忙了磨取,沒功夫給你解析計(jì)算綜合分人柿,只能把所有未處理的原始數(shù)據(jù),包裝成一個(gè)個(gè)消息忙厌,統(tǒng)統(tǒng)找主線程的 handler 拋給 MainLooper 處理凫岖。

我:嗯,是有點(diǎn)糟糕逢净,主線程里是串行處理哥放,這樣萬一碰個(gè)難算的,非卡死不可爹土。要是能有個(gè)子線程幫助主線程甥雕,先把這些原始數(shù)據(jù)接收,將解析胀茵、計(jì)算以及排序等耗時(shí)工作都做完社露,直接給主線程一串排好的分?jǐn)?shù)拿去顯示就好了……

子線程:是啊,要是有這么個(gè)線程就好了(手指來回指自己狀

我:琼娘?莫非你……

子線程:沒錯(cuò)峭弟,我附鸽,就是你們要找的“協(xié)同處理線程”!

我:真敢給自己加戲…想清楚了嗎瞒瘸,這工作你要干的話坷备,首先100條子線程都得找你通訊,你處理完還得把結(jié)果反饋給主線程情臭,你就一路人子線程有個(gè)啥省撑?

子線程:…你還記得我從頭到尾一直嚷嚷著要什么嗎?

我:建 handler 啊…哦俯在,對(duì)對(duì)對(duì)竟秫,哎呀,妹想到妹想到朝巫,handler鸿摇。嘿,其實(shí)我早就看好你劈猿,就知道你要 handler 肯定有你的理由拙吉!

子線程:……

我:那別愣著啊,寫個(gè) Demo 看看效果揪荣。

子線程:且慢筷黔,我有個(gè)條件。

我:仗颈?

子線程:實(shí)不相瞞佛舱,擁有如此有意義的工作的我,已經(jīng)厭倦了作為一條普通路人子線程挨决,乏味地在 run() 方法里三步走才能設(shè)好一個(gè)屬于自己的 handler 的這種感覺了请祖。

我:大家不都這樣嗎……

子線程:并不,你看主線程脖祈,它就沒有這么多規(guī)矩肆捕,Looper 一早就備好了,還自己 loop() 了起來盖高,handler 直接 new 就行慎陵。我是來幫它的,我也要這種待遇喻奥!

我:嗯席纽,要得要得……有想法了,你之前給自己起名叫什么來著撞蚕?

子線程:“協(xié)同處理線程”润梯。

我:行,這樣,你現(xiàn)在就不再是一條普通的Thread了仆救,你的新類名抒和,就叫“HandlerThread”吧!

子線程:還可以彤蔽,你源碼怎么給的。

/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason is isAlive() returns false, this method will return null. If this thread 
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    /**
     * Quits the handler thread's looper.
     * <p>
     * Causes the handler thread's looper to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     *
     * @see #quitSafely
     */
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    /**
     * Quits the handler thread's looper safely.
     * <p>
     * Causes the handler thread's looper to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * Pending delayed messages with due times in the future will not be delivered.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p>
     * If the thread has not been started or has finished (that is if
     * {@link #getLooper} returns null), then false is returned.
     * Otherwise the looper is asked to quit and true is returned.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     */
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}

HandlerThread庙洼,源碼131行顿痪,實(shí)際有效60行,簡明扼要油够。子線程的要求是被如何滿足的呢蚁袭?往 run() 方法里看,prepare 和 loop 一應(yīng)俱全石咬,子線程很是滿意揩悄。

不過要提醒的是,這倆重要操作可都僅存在于 run() 方法里鬼悠,真想建 handler删性,當(dāng)然千萬別忘了把線程先 start() 起來!還有關(guān)于線程的關(guān)閉焕窝,也很有作用蹬挺,讀者可以自行閱讀下源碼注釋。

行了它掂,了解完 HandlerThread 的由來巴帮,最后就附上一個(gè) HandlerThread 的 Demo 吧。先明確功能虐秋,該 HandlerThread榕茧,要廣泛接受其它子線程的消息,自己處理完畢客给,再反饋給主線程用押,實(shí)現(xiàn)效果嘛,就還是狹隘的 UI 更新吧起愈。

public class HandlerThreadActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mButton;
    private TextView mTextView;

    private Thread mUiThread;
    private Handler mUiHandler;

    private HandlerThread mHandlerThread;
    private Handler mZiHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        mButton = (Button) findViewById(R.id.button2);
        mButton.setOnClickListener(this);
        mTextView = (TextView) findViewById(R.id.textView2);
        initUiThreadAndHandler();
        initHandlerThreadAndHandler();
    }

    private void initUiThreadAndHandler() { // 初始化主線程及其 handler
        mUiThread = Thread.currentThread();
        Looper mainLooper = Looper.getMainLooper();
        mUiHandler = new Handler(mainLooper) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 200:
                        mTextView.setText((String) msg.obj);
                        break;
                }
            }
        };
    }

    private void initHandlerThreadAndHandler() { // 初始化 HandlerThread 線程及其 handler
        mHandlerThread = new HandlerThread("HandlerThread");
        mHandlerThread.start(); // 不能忘記哦只恨!
        Looper looper = mHandlerThread.getLooper(); // 不用自己準(zhǔn)備了,直接從當(dāng)前線程拿現(xiàn)成的
        mZiHandler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 100:
                        String result_past = (String) msg.obj;
                        String result_now = result_past + " --> HandlerThread成功接收抬虽,經(jīng)過3秒的處理官觅,向主線程反饋結(jié)果";

                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        Message message = Message.obtain();
                        message.what = 200;
                        message.obj = result_now;
                        mUiHandler.sendMessage(message);
                        break;
                }
            }
        };
    }

    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = Message.obtain();
                message.what = 100;
                message.obj = “雖然想找主線程更新UI,但中途需要請(qǐng)HandlerThread先幫我處理一下數(shù)據(jù)";
                mZiHandler.sendMessage(message);
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandlerThread.quit(); // 必要的停止操作
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阐污,一起剝皮案震驚了整個(gè)濱河市休涤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖功氨,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件序苏,死亡現(xiàn)場離奇詭異,居然都是意外死亡捷凄,警方通過查閱死者的電腦和手機(jī)忱详,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跺涤,“玉大人匈睁,你說我怎么就攤上這事⊥按恚” “怎么了航唆?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長院刁。 經(jīng)常有香客問我糯钙,道長,這世上最難降的妖魔是什么退腥? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任任岸,我火速辦了婚禮,結(jié)果婚禮上阅虫,老公的妹妹穿的比我還像新娘演闭。我一直安慰自己,他們只是感情好颓帝,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布米碰。 她就那樣靜靜地躺著,像睡著了一般购城。 火紅的嫁衣襯著肌膚如雪吕座。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天瘪板,我揣著相機(jī)與錄音吴趴,去河邊找鬼。 笑死侮攀,一個(gè)胖子當(dāng)著我的面吹牛锣枝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兰英,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼撇叁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了畦贸?” 一聲冷哼從身側(cè)響起陨闹,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤楞捂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后趋厉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寨闹,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年君账,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了繁堡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杈绸,死狀恐怖帖蔓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞳脓,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布澈侠,位于F島的核電站劫侧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏哨啃。R本人自食惡果不足惜烧栋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拳球。 院中可真熱鬧审姓,春花似錦、人聲如沸祝峻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莱找。三九已至酬姆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奥溺,已是汗流浹背辞色。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浮定,地道東北人相满。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像桦卒,于是被迫代替她去往敵國和親立美。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

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