AsyncTask 源碼解析 (Android Learning Note)

工作流程簡(jiǎn)述

AsyncTask 涉及的知識(shí)點(diǎn)有 Handler撤摸,Thread框冀,Callable顶霞,F(xiàn)utureTask等蓝翰。

大體流程圖如下:


AsyncTask

簡(jiǎn)單介紹下:
AsyncTask 的構(gòu)造器創(chuàng)建了一個(gè) mFuture(FutureTask) 和 mWorker(WorkerRunnable),mFuture 重寫了 done 方法贯钩,mWorker 實(shí)現(xiàn)了 call 方法募狂,將 mWorker 傳入 mFuture。
構(gòu)建完 AsyncTask 對(duì)象角雷,就可以執(zhí)行 execute 方法祸穷,execute 方法中調(diào)用了 executeOnExecutor,并傳入了默認(rèn)的 sDefaultExecutor 勺三,sDefaultExecutor 默認(rèn)是模擬的單線程(下面會(huì)詳細(xì)說怎么模擬的)雷滚,AsyncTask 的任務(wù)只會(huì)一個(gè)一個(gè)執(zhí)行。也可以傳入自定義的線程檩咱,并發(fā)執(zhí)行任務(wù)揭措。在 executeOnExecutor 方法中胯舷,執(zhí)行下載前的準(zhǔn)備方法 onPreExecute(); 刻蚯。然后 Executor 執(zhí)行 mFuture(FutureTask),此時(shí)會(huì)調(diào)用 mFuture 的 run();桑嘶。run() 方法中會(huì)調(diào)用 mWorker 中的 call();炊汹,此時(shí)在子線程中會(huì)調(diào)用 doInBackground(),我們的耗時(shí)任務(wù)就是在這里實(shí)現(xiàn)的逃顶。耗時(shí)任務(wù)結(jié)束后讨便,拿到返回值調(diào)用 postResult()充甚,這里創(chuàng)建了一個(gè) Message (what = MESSAGE_POST_RESULT)帶上結(jié)果 result 發(fā)送給了處理消息的 InternalHandler 對(duì)象。sHandler 收到消息之后如果 what 是 MESSAGE_POST_RESULT霸褒,則判斷任務(wù)是否被取消 isCancelled() 伴找。如果取消了則調(diào)用 onCancelled(result) 沒有取消則調(diào)用 onPostExecute(result)
doInBackground() 中废菱,我們還可以調(diào)用 publishProcess() 方法技矮,在主線程中刷新進(jìn)度,方法中創(chuàng)建了一個(gè) Message (what = MESSAGE_POST_PROGRESS)帶上進(jìn)度值 value殊轴,同樣發(fā)送給 sHandler 處理衰倦,sHandler 處理消息,如果 what 是 MESSAGE_POST_PROGRESS 則調(diào)用 onProgressUpdate(Progress... values) 方法旁理。
onPreExecute();樊零,onCancelled(result)onPostExecute(result)孽文,onProgressUpdate(Progress... values) 這些方法都是在主線程中執(zhí)行驻襟,只有 doInBackground() 是子線程中執(zhí)行。

AsyncTask 概念

AsyncTask 芋哭,(以下翻譯自官方文檔)能夠正確塑悼,容易的使用 UI 線程。這個(gè)類允許你執(zhí)行后臺(tái)操作并將結(jié)果呈現(xiàn)在 UI 線程上楷掉,不用你去操作 threads 和 handlers厢蒜。

AsyncTask 被設(shè)計(jì)為一個(gè) Thread 和 Handler 的輔助類,并不是構(gòu)建線程的框架烹植。理想情況下斑鸦,AsyncTask 被用于短時(shí)間操作(大部分是幾秒鐘的),如果你需要保持線程長時(shí)間運(yùn)行草雕,非常推薦你使用 java.util.concurrent 包的一些 APIs巷屿,如 Executor,ThreadPoolExecutor 和 FutureTask墩虹。

異步任務(wù)由在后臺(tái)線程運(yùn)行嘱巾,在 UI 線程呈現(xiàn)結(jié)果的計(jì)算定義。異步任務(wù)由三個(gè)普通類型诫钓,Params旬昭,Progress,Result 和四個(gè)步驟菌湃,分別是 onPreExecute, doInBackground, onProgressUpdate and onPostExecute 來定義问拘。

代碼示例:

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
 
     @Override
     protected void onPreExecute() {
        //準(zhǔn)備工作,在主線程。
     }
 
     protected Long doInBackground(URL... urls) {
         //耗時(shí)操作骤坐,在子線程绪杏。
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     @Override
     protected void onProgressUpdate(Integer... progress) {
         //顯示進(jìn)度,在主線程
         setProgressPercent(progress[0]);
     }
     
     @Override
     protected void onCancelled(Float result) {
         //任務(wù)被取消會(huì)調(diào)用這個(gè)方法纽绍,不會(huì)調(diào)用 onPostExecute 蕾久,在主線程。
         showDialog("Cancelled " + result + " bytes");
     }

     @Override
     protected void onPostExecute(Long result) {
         //任務(wù)完成拌夏,沒有被取消腔彰,調(diào)用這個(gè)方法,在主線程辖佣。
         showDialog("Downloaded " + result + " bytes");
     }
 }
 
 //使用
 new DownloadFilesTask().execute(url1, url2, url3);

接下來結(jié)合這段代碼示例霹抛,前面的工作流程圖和源代碼,看下 AsyncTask 如何在 UI 線程和子線程之間切換卷谈,如何使用三個(gè)范型杯拐,四個(gè)步驟如何調(diào)用。
先看 AsyncTask 的構(gòu)造方法:

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();
            return postResult(result);
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                postResultIfNotInvoked(get());
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occurred while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}

構(gòu)造方法中創(chuàng)建了一個(gè) mWorker(WorkerRunnable)世蔗,它實(shí)現(xiàn)的是 Callable 接口端逼,和 Runnable 接口的區(qū)別是,實(shí)現(xiàn)方法有返回值 V call() throws Exception;污淋,call() 方法的代碼后面會(huì)做說明顶滩。
同時(shí)還創(chuàng)建了一個(gè) mFuture(FutureTask),并將 mWorker 做為參數(shù)傳入寸爆。mFuture 重寫了 done() 方法礁鲁,這里也是后面調(diào)用到再做說明。

AsyncTask 創(chuàng)建出來后赁豆,需要調(diào)用 public final AsyncTask<Params, Progress, Result> execute(Params... params) 方法來這執(zhí)行這個(gè)任務(wù)仅醇。
這個(gè)方法里調(diào)用的是 executeOnExecutor(sDefaultExecutor, params);,傳入的是 sDefaultExecutor 和 params 對(duì)應(yīng)的參數(shù)魔种。我們先看 sDefaultExecutor 到底是什么析二?

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

sDefaultExecutor 的值是一個(gè)內(nèi)部類 SerialExecutor 的對(duì)象 SERIAL_EXECUTOR,這個(gè)類實(shí)現(xiàn) Executor 接口节预。為什么說它是模擬的單線程呢叶摄?一開始我以為它是一個(gè)單線程的線程池,看了代碼之后發(fā)現(xiàn)并不是安拟,任務(wù)是 THREAD_POOL_EXECUTOR 這個(gè)自定義線程池執(zhí)行的蛤吓。任務(wù)維護(hù)在 ArrayDeque 這個(gè)隊(duì)列中,SERIAL_EXECUTOR 執(zhí)行 execute 方法就會(huì)創(chuàng)建一個(gè)任務(wù)放入這個(gè)隊(duì)列去扣。當(dāng) mActive == null 的時(shí)候柱衔,說明之前隊(duì)列中還沒有任務(wù),然后執(zhí)行 scheduleNext(); ,從隊(duì)列中取出任務(wù)賦值給 mActive,并由 THREAD_POOL_EXECUTOR 來執(zhí)行任務(wù)闭树,這時(shí)會(huì)執(zhí)行任務(wù)的 run() 方法琐旁,而 run() 方法中又會(huì)執(zhí)行傳進(jìn)來的那個(gè)任務(wù) final Runnable r,執(zhí)行完后窿锉,同樣調(diào)用 scheduleNext(); 再去取下一個(gè)任務(wù),如此循環(huán),直到隊(duì)列中沒有任務(wù)為止王浴。
THREAD_POOL_EXECUTOR 這個(gè)自定義線程的創(chuàng)建過程看如下代碼:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128); 
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
        TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

介紹完 sDefaultExecutor 后,回到主線梅猿,看方法 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params)氓辣。

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            case RUNNING:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task is already running.");
            case FINISHED:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task has already been executed "
                        + "(a task can be executed only once)");
        }
    }

    mStatus = Status.RUNNING;

    onPreExecute();

    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}

方法中,先是校驗(yàn)任務(wù)狀態(tài)袱蚓,如果是 RUNNING 已運(yùn)行或是 FINISHED 已完成都會(huì)拋出異常钞啸。然后,修改任務(wù)狀態(tài)為 RUNNING喇潘。之后再執(zhí)行 onPreExecute(); 方法体斩,此時(shí)重要的四個(gè)步驟的第一步就被執(zhí)行了。之后將傳入的參數(shù) params 交給 mWorker颖低,執(zhí)行 mFuture絮吵。
此時(shí),和我們之前介紹的 sDefaultExecutor 執(zhí)行過程 和 AsyncTask 的構(gòu)造方法就要聯(lián)系起來了忱屑。調(diào)用 exec.execute(mFuture); 方法將 mFuture 包裝成任務(wù)放入隊(duì)列(前面說過的)蹬敲,隨后被執(zhí)行 mFuture.run()。因?yàn)?FutureTask 實(shí)現(xiàn)了 Runnable 接口莺戒,所以會(huì)有對(duì)應(yīng)的 run() 方法粱栖。

我們找到 FutureTask 類來看下它的 run() 方法是如何實(shí)現(xiàn)的。

public class FutureTask<V> implements RunnableFuture<V> {
...
    public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    
    protected void set(V v) {
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = v;
            U.putOrderedInt(this, STATE, NORMAL); // final state
            finishCompletion();
        }
    }
    
    private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (U.compareAndSwapObject(this, WAITERS, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
    
        done();
    
        callable = null;        // to reduce footprint
    }
...
}

代碼中的 callable 就是前面構(gòu)造器中傳入 mFuture 的 mWorker脏毯。Callable<V> c = callable; result = c.call(); 可以看到 mWorker 被調(diào)用了闹究,隨后又調(diào)用 set(result);,將結(jié)果賦值給
outcome,調(diào)用 finishCompletion()食店,最后調(diào)用 done()渣淤。done 方法中主要是調(diào)用 postResultIfNotInvoked(get()); 來校驗(yàn)(mTaskInvoked標(biāo)識(shí)位)如果任務(wù)沒有執(zhí)行,也保證執(zhí)行postResult(result); 方法吉嫩,把結(jié)果返回給主線程价认。

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
        postResult(result);
    }
}

此時(shí) mWorker 的 call() 和 mFuture 的 done() 方法都被調(diào)用了。

我們?cè)诜厝タ?mWorker 的 call() 方法自娩。

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        mTaskInvoked.set(true);

        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        //noinspection unchecked
        Result result = doInBackground(mParams);
        Binder.flushPendingCommands();
        return postResult(result);
    }
};

首先設(shè)置任務(wù)被調(diào)用的標(biāo)識(shí)為 true用踩,設(shè)置線程優(yōu)先級(jí)了 THREAD_PRIORITY_BACKGROUND渠退,再調(diào)用方法 doInBackground(mParams),這是四個(gè)步驟的第二步方法被調(diào)用脐彩,耗時(shí)任務(wù)開始執(zhí)行碎乃。獲取到返回的結(jié)果后,調(diào)用 postResult(result); 方法惠奸。
對(duì) Binder.flushPendingCommands(); 作用并不清楚梅誓。我們先看 postResult。

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

private static Handler getHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler();
        }
        return sHandler;
    }
}

創(chuàng)建一個(gè) Message佛南,what 是 MESSAGE_POST_RESULT梗掰,用返回值創(chuàng)建一個(gè) AsyncTaskResult 對(duì)象做為message 的 obj,并發(fā)送出去嗅回,交給 sHandler 來處理及穗,我們看 Handler 處理消息的代碼。

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

handleMessage 方法中處理收到的消息绵载,如果消息是 MESSAGE_POST_RESULT拥坛,調(diào)用 finish 方法。 finish 方法中根據(jù)任務(wù)是否被取消尘分,來執(zhí)行不同的方法猜惋,如果取消則執(zhí)行 onCancelled(result);,沒有取消執(zhí)行 onPostExecute(result);培愁,此時(shí) AsyncTask 的第四步驟就走完了著摔。整個(gè)任務(wù)執(zhí)行完畢《ㄐ可以看到 onCancelled 和 onPostExecute 只會(huì)執(zhí)行一個(gè)谍咆。
如果是 MESSAGE_POST_PROGRESS 的消息,則執(zhí)行 onProgressUpdate(result.mData) 方法私股,這個(gè)是第三個(gè)步驟摹察,這個(gè)消息是怎么來的呢?

@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

原來我們可以在工作線程中調(diào)用這個(gè)方法倡鲸,也就是在 doInBackground 中使用供嚎。如果任務(wù)沒有取消,就會(huì)創(chuàng)建一個(gè) what 是 MESSAGE_POST_PROGRESS峭状,將進(jìn)度值封裝成一個(gè) AsyncTaskResult 對(duì)象做為 obj 的 Message 給 Handler 處理克滴。

最后看下,任務(wù)是怎么被取消的优床,調(diào)用如下方法:

public final boolean cancel(boolean mayInterruptIfRunning) {
    mCancelled.set(true);
    return mFuture.cancel(mayInterruptIfRunning);
}

修改是否取消的標(biāo)識(shí)位劝赔,調(diào)用 mFuture 的 cancel 方法。mFuture 的 cancel 方法中胆敞,如果有當(dāng)前任務(wù)的線程則會(huì)調(diào)用這個(gè)線程的 interrupt 的方法并也會(huì)調(diào)用 finishCompletion();着帽,判斷任務(wù)是否調(diào)用杂伟,發(fā)送 postResult。

總結(jié)

AsyncTask 不僅能幫我們簡(jiǎn)單的完成異步任務(wù)的操作仍翰,還就如何更好更準(zhǔn)確的使用 Handler 和 Thread 做出了示范赫粥,從其中能學(xué)到很多有用的知識(shí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歉备,一起剝皮案震驚了整個(gè)濱河市傅是,隨后出現(xiàn)的幾起案子匪燕,更是在濱河造成了極大的恐慌蕾羊,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帽驯,死亡現(xiàn)場(chǎng)離奇詭異龟再,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尼变,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門利凑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嫌术,你說我怎么就攤上這事哀澈。” “怎么了度气?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵割按,是天一觀的道長。 經(jīng)常有香客問我磷籍,道長适荣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任院领,我火速辦了婚禮弛矛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘比然。我一直安慰自己丈氓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布强法。 她就那樣靜靜地躺著扒寄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拟烫。 梳的紋絲不亂的頭發(fā)上该编,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音硕淑,去河邊找鬼课竣。 笑死嘉赎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的于樟。 我是一名探鬼主播公条,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼迂曲!你這毒婦竟也來了靶橱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤路捧,失蹤者是張志新(化名)和其女友劉穎关霸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杰扫,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡队寇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了章姓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佳遣。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖凡伊,靈堂內(nèi)的尸體忽然破棺而出零渐,到底是詐尸還是另有隱情,我是刑警寧澤系忙,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布诵盼,位于F島的核電站,受9級(jí)特大地震影響笨觅,放射性物質(zhì)發(fā)生泄漏拦耐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一见剩、第九天 我趴在偏房一處隱蔽的房頂上張望杀糯。 院中可真熱鬧,春花似錦苍苞、人聲如沸固翰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骂际。三九已至,卻和暖如春冈欢,著一層夾襖步出監(jiān)牢的瞬間歉铝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工凑耻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留太示,地道東北人柠贤。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像类缤,于是被迫代替她去往敵國和親臼勉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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