AsyncTask使用總結(jié)及源碼分析

AsyncTask

AsyncTask是android再API-3中加入的類,為了方便開發(fā)人員執(zhí)行后臺操作并在UI線程上發(fā)布結(jié)果而無需操作Threads和handlers。AsyncTask的設計是圍繞Threads和handlers的一個輔助類,不構(gòu)成一個通用的線程框架。其用于時間較短的網(wǎng)絡請求,例如登錄請求。對于下載文件這種長時間的網(wǎng)絡請求并不合適。
?AsyncTask是一個抽象類,使用時必須繼承AsyncTask并實現(xiàn)其protected abstract Result doInBackground(Params... params);方法。在繼承時可以為AsyncTask類指定三個泛型參數(shù),這三個參數(shù)的用途如下:

  1. Params
    在執(zhí)行AsyncTask時需要傳入的參數(shù),可用于在后臺任務中使用。
  2. Progress
    后臺任何執(zhí)行時,如果需要在界面上顯示當前的進度,則使用這里指定的泛型作為進度單位。
  3. Result
    當任務執(zhí)行完畢后,如果需要對結(jié)果進行返回,則使用這里指定的泛型作為返回值類型。

舉個例子:

public class MainActivity extends AppCompatActivity{
    private static final String TAG = "MainActivity";
    private ProgressDialog mDialog;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mDialog = new ProgressDialog(this);
        mDialog.setMax(100);
        mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mDialog.setCancelable(false);

        new myAsycnTask().execute();

    }
    private  class myAsycnTask extends AsyncTask<Void,Integer,Void>{
        @Override
        protected void onPreExecute() {
           //執(zhí)行任務之前,準備操作
            super.onPreExecute();
            mDialog.show();
        }

        @Override
        protected Void doInBackground(Void... params) {
            //任務執(zhí)行過程中 
            for (int i =0 ;i<100;i++)
            {
                SystemClock.sleep(100);
                publishProgress(i);
            }
            return null;
        }
        @Override
        protected void onProgressUpdate(Integer... values) {
           //任務執(zhí)行過程中更新狀態(tài)
            super.onProgressUpdate(values);
            mDialog.setProgress(values[0]);
        }
        @Override
        protected void onPostExecute(Void aVoid) {
            //任務執(zhí)行完成之后
            super.onPostExecute(aVoid);
            mDialog.dismiss();
        }
    }
}

AsyncTask 異步加載數(shù)據(jù)可能需要重寫以下幾個方法:

  1. onPreExecute()
    這個方法會在后臺任務開始執(zhí)行之間調(diào)用,用于進行一些的初始化操作。
  2. doInBackground(Params...)
    子類必須重寫該方法,這個方法中的所有代碼都會在子線程中執(zhí)行,處理耗時任務。任務完成后可以通過return語句將結(jié)果返回,如果AsyncTask的第三個泛型參數(shù)指定的是Void,就可以不返回任務執(zhí)行結(jié)果。在這個方法中不可以進行UI操作,如果需要更新UI元素,可以調(diào)用publishProgress(Progress...)方法來完成。
  3. onProgressUpdate(Progress...)
    當在doInBackground(Params...)中調(diào)用了publishProgress(Progress...)方法后,會調(diào)用該方法,參數(shù)為后臺任務中傳遞過來Progress...。在這個方法中可以對UI進行操作,利用參數(shù)中的數(shù)值就可以對UI進行相應的更新。
  4. onPostExecute(Result)
    當后臺任務執(zhí)行完畢并通過 return語句進行返回時,方法會被調(diào)用。返回的數(shù)據(jù)會作為參數(shù)傳遞到此方法中,可以利用返回的數(shù)據(jù)來進行一些UI操作。

通過調(diào)用cancel(boolean)方法可以隨時取消AsyncTask任務,調(diào)用該方法之后會隨后調(diào)用iscancelled()并返回true,doInBackground(Object[])返回之后會直接調(diào)用onCancelled(Object)方法而不是正常調(diào)用時的onPostExecute(Result)

注意:

1、AsyncTask必須在UI線程。
?2、必須在UI線程上創(chuàng)建任務實例。
?3、execute(Params...) 必須在UI線程中調(diào)用。
?4、不要手動調(diào)用onpreexecute(),onpostexecute(Result),doInBackground(Params…),onProgressUpdate(Progress…)這些方法。
?5、任務只能執(zhí)行一次(如果嘗試執(zhí)行第二次執(zhí)行將拋出異常)。

使用AsyncTask不需要考慮異步消息處理機制,也不需要考慮UI線程和子線程,只需要調(diào)用一下publishProgress()方法就可以輕松地從子線程切換到UI線程了。從android 3.0之后AsyncTask使用單線程處理所有任務。

AsyncTask源碼分析

我們使用AsyncTask時,new了一個myAsycnTask對象然后執(zhí)行了execute()方法new myAsycnTask().execute(); 。那就先看一下AsyncTask的構(gòu)造函數(shù)和execute()方法:

 /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return 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);
                }
            }
        };
    }
/**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     */
    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
 private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }
/**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Callable}.
     */
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

構(gòu)造函數(shù)中初始化了兩個參數(shù),在mWorker中進行了初始化操作,并在初始化mFuture的時候?qū)?code>mWorker作為參數(shù)傳入。mWorker是一個實現(xiàn)了Callable<Result>接口的Callable對象,mFuture是一個FutureTask對象,創(chuàng)建時設置了mWorker的回調(diào)方法,然后設置狀態(tài)為this.state = NEW; (該狀態(tài)是FutureTask類中的) 。

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, 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;
    }
  /**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

可以看到execute()方法調(diào)用了executeOnExecutor(sDefaultExecutor, params)方法,該方法中,首先檢查當前任務狀態(tài), PENDING,代表任務還沒有執(zhí)行;RUNNING,代表任務正在執(zhí)行;FINISHED,代表任務已經(jīng)執(zhí)行完成。如果任務沒有執(zhí)行,設置當前狀態(tài)為正在執(zhí)行,調(diào)用了onPreExecute();方法,因此證明了onPreExecute()方法會第一個得到執(zhí)行。那doInBackground(Void... params)在什么地方調(diào)用的呢?看方法最后執(zhí)行了exec.execute(mFuture);,這個exec就是execute(Params... params)中傳過來的sDefaultExecutor參數(shù),讓我們分析一下它做了什么。

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
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是一個SerialExecutor對象,其execute(final Runnable r)方法中的r就是exec.execute(mFuture);中的mFuture
?SerialExecutor是使用ArrayDeque這個隊列來管理Runnable對象的,如果一次性啟動多個任務,在第一次運行execute()方法的時候,會調(diào)用ArrayDequeoffer()方法將傳入的Runnable對象添加到隊列的尾部,然后判斷mActive對象是不是等于null,第一次運行是等于null的,于是會調(diào)用scheduleNext()方法。在這個方法中會從隊列的頭部取值,并賦值給mActive對象,然后調(diào)用THREAD_POOL_EXECUTOR去執(zhí)行取出的取出的Runnable對象。之后如何又有新的任務被執(zhí)行,同樣還會調(diào)用offer()方法將傳入的Runnable添加到隊列的尾部,但是再去給mActive對象做非空檢查的時候就會發(fā)現(xiàn)mActive對象已經(jīng)不再是null了,于是就不會再調(diào)用scheduleNext()方法。
?那么后面添加的任務豈不是永遠得不到處理了?當然不是,看一看offer()方法里傳入的Runnable匿名類,這里使用了一個try finally代碼塊,并在finally中調(diào)用了scheduleNext()方法,保證無論發(fā)生什么情況,這個方法都會被調(diào)用。也就是說,每次當一個任務執(zhí)行完畢后,下一個任務才會得到執(zhí)行,SerialExecutor模仿的是單一線程池的效果,如果我們快速地啟動了很多任務,同一時刻只會有一個線程正在執(zhí)行,其余的均處于等待狀態(tài)

看看它在子線程里做了什么:

private Callable<V> callable;
ublic FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
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);
        }
    }

可以看到mFuture中的run()方法調(diào)用了構(gòu)造函數(shù)中傳過來的callable對象的call()方法,也就是AsyncTask的構(gòu)造函數(shù)中的mWorkercall()方法,在這個方法中調(diào)用了result = doInBackground(mParams);

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

終于找到了doInBackground()方法的調(diào)用處,雖然經(jīng)過了很多周轉(zhuǎn),但目前的代碼仍然是運行在子線程當中的,所以這也就是為什么我們可以在doInBackground()方法中去處理耗時的邏輯。
?call()在最后執(zhí)行了postResult(result);函數(shù),好的,繼續(xù)往下看。

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

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

這段代碼對是不是很熟悉?這里使用getHandler()返回的sHandler對象發(fā)出了一條消息,消息中攜帶了MESSAGE_POST_RESULT常量和一個表示任務執(zhí)行結(jié)果的AsyncTaskResult對象。這個sHandler對象是InternalHandler類的一個實例,那么稍后這條消息肯定會在InternalHandler的handleMessage()方法中被處理。InternalHandler的源碼如下所示:

    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;
            }
        }
    }

可以看到,根據(jù)不同的消息進行了判斷,如果是MESSAGE_POST_RESULT消息則執(zhí)行result.mTask.finish(result.mData[0]);;如果是MESSAGE_POST_PROGRESS則執(zhí)行result.mTask.onProgressUpdate(result.mData);,先讓我們看看AsyncTaskResult<?> result到底是什么?

@SuppressWarnings({"RawUseOfParameterizedType"})
    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }

可以看到``中包含兩個參數(shù),一個是AsyncTask當前的AsyncTask對象,一個是 Data[]返回數(shù)據(jù)的數(shù)組。所以result.mTask.finish(result.mData[0]);result.mTask.onProgressUpdate(result.mData);就是調(diào)用 AsyncTask的finsh和onProgressUpdate方法。讓我們看一下:

public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }
 public final boolean isCancelled() {
        return mCancelled.get();
    }
private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

就像文章開頭說的,任務可以隨時取消,如果取消會調(diào)用isCancelled()返回true,并且onCancelled(result);會代替onPostExecute(result);執(zhí)行。如果沒有取消的話就會直接執(zhí)行onPostExecute(result);方法。
?好了,還剩下publishProgress(Progress...)onProgressUpdate(Progress...)方法了。上面說到sHandler對象中有兩種消息,還有一種是MESSAGE_POST_PROGRESS執(zhí)行result.mTask.onProgressUpdate(result.mData);方法,讓我們看一下publishProgress(Progress...)

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

非常明確了,在publishProgress(Progress...)方法中發(fā)送了MESSAGE_POST_PROGRESS然后在onProgressUpdate(Progress...)方法中對消息的數(shù)據(jù)進行了處理。正因如此,在doInBackground()方法中調(diào)用publishProgress()方法才可以從子線程切換到UI線程,從而完成對UI元素的更新操作。
?最后我們看一下取消任務是怎么實現(xiàn)的:

//AsycnTask中的cancel方法
public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }

//mFuture = new FutureTask<Result>(mWorker);類中的cancel方法
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              U.compareAndSwapInt(this, STATE, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }
    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
    }

可以看到當調(diào)用AsyncTask中的cancel(boolean mayInterruptIfRunning)方法時會傳入一個boolean值,
該值表示是否允許當前運行中的任務執(zhí)行完畢,true代表不允許,false代表允許。然后調(diào)用了mFuture.cancel(mayInterruptIfRunning)方法,該方法中會判斷mayInterruptIfRunning的值,如果為true則調(diào)用線程的interrupt();方法,中斷請求,然后執(zhí)行finishCompletion()方法;如果為false則直接執(zhí)行finishCompletion()方法。finishCompletion()方法將隊列中的所有等待任務刪除。

注意:
?AsyncTask不會不考慮結(jié)果而直接結(jié)束一個線程。調(diào)用cancel()其實是給AsyncTask設置一個"canceled"狀態(tài)。這取決于你去檢查AsyncTask是否已經(jīng)取消,之后決定是否終止你的操作。對于mayInterruptIfRunning——它所作的只是向運行中的線程發(fā)出interrupt()調(diào)用。在這種情況下,你的線程是不可中斷的,也就不會終止該線程。
?可以使用isCancelled()判斷任務是否被取消,然后做相應的操作。

參考文獻:

http://blog.csdn.net/pi9nc/article/details/12622797
https://developer.android.google.cn/reference/android/os/AsyncTask.html#cancel(boolean)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

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