Android AsyncTask工作機制源碼剖析

前言

上一篇文章我們一起分析了Android消息機制的實現原理,通過分析我們知道,Android系統規定不能在主線程(UI線程)中執行耗時操作,這就需要我們在子線程中處理耗時操作,然后在執行完耗時操作后我們可以通過Handler發送一個消息給主線程通知其進行刷新UI等操作。如果對這一塊知識點還不清楚,請參考前面的文章Android Handler-帶你從源碼理解消息機制。而本篇文章要說到的是AsyncTask這個類,這個類就是專門用來處理異步任務的,它完成了對Thread和Handler的封裝,我們只需在它的doInBackground方法中執行我們的耗時操作,在執行完耗時操作后,它的內部會完成調用Handler向主線程發送完成消息的邏輯,而不需要我們自行去處理。接下來就讓我們一起走進它的源碼,來分析一下它內部的實現原理(文章參考sdk29源碼進行分析)。

AsyncTask

1.基本用法:

在AsyncTask的源碼注釋中,列舉了AsyncTask的一個最基本的使用方式,如下:

private class DownloadFilesTask extends AsyncTask(URL, Integer, Long) {
    protected Long doInBackground(URL... urls) {
        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;
     }

    protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
 
     protected void onPostExecute(Long result) {
        showDialog("Downloaded " + result + " bytes");
    }
}

AsyncTask是一個抽象的范型類,內部存在一個抽象方法doInBackground,我們需要創建一個類繼承它,并實現它的doInBackground方法,在這個方法的內部我們來實現具體的耗時操作的邏輯。它的三個范型參數分別為Params, Progress, Result,其中Params為執行任務時發送給任務的參數類型,doInBackground方法中的參數類型就是和這個Params類型一致的,上例中指定成了URL類型;Progress為任務執行的進度類型,通常指定為Integer類型;最后一個Result為任務返回結果的類型。當我們要通過上面的DownloadFilesTask執行下載文件的操作時,我們只需創建DownloadFilesTask實例并調用它的execute方法即可,在execute方法中我們需要傳入任務執行所需的參數,即Params范型所對應的類型參數。

2.執行步驟:

一個AsyncTask任務從開始執行到結束,一共會經歷四個步驟,它們分別是:

2.1.onPreExecute:

在任務開始執行之前在主線程中被調用,通常用來進行一些準備工作,比如顯示一個進度條。

2.2.doInBackground:

在執行完onPreExecute方法之后,該方法就會被調用。此方法用于執行耗時操作,異步任務的參數傳遞到此方法中,此方法會返回一個計算結果并將其傳遞到最后一步onPostExecute方法中。在操作執行的過程中,還可以調用publishProgress方法將操作執行的進度傳遞到onProgressUpdate方法中。

2.3.onProgressUpdate:

此方法用于在耗時操作執行的過程中,在用戶界面以任意的形式展示當前操作執行的進度,該方法是在主線程中被調用的。

2.4.onPostExecute:

在耗時操作執行完畢時,該方法會在主線程中被調用,并且耗時操作返回的結果將被傳入到此方法的參數中。

在前面的AsyncTask使用示例中,我們已經看到了其中的三個步驟方法,通常情況下,除了doInBackground方法是必須被重寫的,其他三個方法我們可以根據自身的需求來選擇性的去使用。

3.注意事項:

在使用AsyncTask類的時候,這里有一些注意事項還是要知道的:

    1. AsyncTask類必須在主線程中完成加載。
    1. AsyncTask實例必須在主線程中創建。
    1. AsyncTask的execute方法必須在主線程中調用。
    1. 不要手動調用它的onPreExecute、onPostExecute、doInBackground、onProgressUpdate方法。
    1. 一個AsyncTask任務只能被執行一次,否則會在第二次調用execute方法時拋出異常。

4.走進源碼:

4.1構造方法:

AsyncTask的構造方法一共有三個,我們先來看下這三個方法的源代碼:

public AsyncTask() { this((Looper) null); }

public AsyncTask(@Nullable Handler handler) { this(handler != null ? handler.getLooper() : null); }

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler() : new Handler(callbackLooper);
    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);
            }
        }
    };
}

通常情況下我們調用它的無參構造來創建一個AsyncTask,而在這個無參構造的內部調用的是它的帶有一個Looper 類型參數的構造,在這個構造方法中會初始化三個變量,分別為mHandler、mWorker、mFuture,而這三個實例對應的類型分別為InternalHandler、WorkerRunnable、FutureTask,這三個變量可以說是AsyncTask完成異步任務過程中的重要角色,把這三個變量的意義弄懂基本上后面的任務執行流程就能很輕松的理解了。

4.2.InternalHandler:

首先說一下這個InternalHandler,這個類是AsyncTask的一個靜態內部類,繼承自Handler,只不過它的內部重寫了handleMessage方法,源碼如下:

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

    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

在它的handleMessage方法中,只會處理兩種類型的消息,一種是MESSAGE_POST_RESULT類型的消息,也就是當任務執行完成時發送出的消息;而另一種是MESSAGE_POST_PROGRESS類型的消息,也就是在任務執行過程中更新進度的消息。在這個方法中可以看到一個AsyncTaskResult類,這個類也是AsyncTask的一個靜態內部類,它的源碼如下:

private static class AsyncTaskResult<Data> {
    final AsyncTask mTask;
    final Data[] mData;
    AsyncTaskResult(AsyncTask task, Data... data) {
        mTask = task;
        mData = data;
    }
}

這個類可以看成是一個數據承載類,在AsyncTask的工作機制中它會被賦值成InternalHandler發送的消息中的obj變量,它會承載兩種類型的數據,一種就是任務執行過程中的進度數據,一種就是任務執行完成的結果數據。在AsyncTaskResult類的內部擁有兩個成員變量mTask和mData,其中mTask為AsyncTask類型,在所有創建AsyncTaskResult實例的地方都會將其指向當前的AsyncTask;另一個mData是一個數組,當AsyncTaskResult承載的是進度數據時,mData的類型會與AsyncTask類的Progress泛型一致;而當AsyncTaskResult承載的是結果數據時,mData的類型會與AsyncTask類的Result泛型一致。現在回到InternalHandler的handleMessage方法,當接收的消息為MESSAGE_POST_RESULT類型時,會調用到AsyncTask的finish方法,源碼如下:

private void finish(Result result) {
    if (isCancelled()) { // isCancelled方法的內部會判斷當前的任務是否已經取消掉
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

在finish方法的內部會進行判斷,如果當前的任務已經被cancel掉了,那么就調用onCancelled方法(內部再調用onCancelled的無參重載方法,是一個空方法,可以根據需要來實現具體的邏輯);如果當前任務沒有被cancel掉,就會調用前面我們說過的onPostExecute方法,這個方法本身也是一個空方法,我們可以重寫這個方法并在方法內部對任務執行的結果進行相應的處理,最后在方法的結尾處會將mStatus變量的值置為Status.FINISHED,此時代表任務執行完成;再次回到InternalHandler的handleMessage方法中,當接收的消息為MESSAGE_POST_PROGRESS類型時,會調用AsyncTask的onProgressUpdate方法,這個方法前面也是說到過,也是一個空方法,我們可以重寫這個方法在里面進行一些更新任務執行進度的提示操作。

4.3.WorkerRunnable:

這個WorkerRunnable是AsyncTask的一個靜態內部類,它實現了Callable接口,并且它的范型和AsyncTask的兩個范型相對應,源代碼如下:

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    Params[] mParams;
}

在它的內部只有一個mParams數組變量,類型與Params范型一致,現在再來看一下在構造方法中mWorker變量的初始化方式:

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        /**
         * 這個mTaskInvoked是一個AtomicBoolean類型的變量;
         * 一旦當前AsyncTask任務中的mWorker的call被調用,這個mTaskInvoked變量中的value值就會被置為1;
         * 在AtomicBoolean類的內部存在一個value變量,值為1時代表true,值為0時代表false;
         */ 
        mTaskInvoked.set(true); 
        Result result = null;
        try {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            result = doInBackground(mParams); 
            Binder.flushPendingCommands();
        } catch (Throwable tr) {
            mCancelled.set(true);
            throw tr;
        } finally {
            postResult(result);
        }
        return result;
    }
};

因為WorkerRunnable是一個抽象類,所以只能通過匿名類的方式創建它的實例,其內部實現的call方法是Callable接口中的。在這個call方法的內部我們看到了一個非常關鍵的方法,即前面說到的用來執行耗時任務的doInBackground方法,通過doInBackground方法執行具體的耗時任務并返回一個任務結果,在call方法的最后會調用postResult方法將返回的任務結果發送給mHandler處理,方法的源碼如下:

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

在方法的內部就是通過mHandler發送處一條MESSAGE_POST_RESULT類型的消息,消息的obj變量為一個范型被指定Result類型的AsyncTaskResult對象,最終這個消息會被mHandler處理,至于消息處理的邏輯前面剛剛講過這里不再贅述。通過分析mWorker的call方法,我們可以知道在AsyncTask中,mWorker扮演著任務的執行者角色,一個AsyncTask任務是在mWorker的call方法中被真正執行的。

4.4.FutureTask:

這個類并不是AsyncTask的內部類,也并不是單單只會出現在AsyncTask的使用場景中,它可以理解成一個將要被執行的任務,在執行之前存在被取消掉的可能,這個類間接的實現了Runnable接口。在前面的AsyncTask的構造方法中,它的實例化方式如下:

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

這里調用它的一個參數的構造方法,將剛剛的mWorker變量傳入FutureTask中,并且重寫了它的done方法。在后面我們要說到的AsyncTask的execute方法中,這個mFuture變量最終會被交給一個線程池去處理,當線程池調用execute方法執行這個FutureTask時,這個FutureTask的run方法將會被調用(FutureTask實現了Runnable接口,這里需要對線程以及線程池的內部原理有一定的了解),而在FutureTask的run方法中,在構造方法中傳入的mWorker變量的call方法又將會得到調用(WorkerRunnable實現了Callable接口),從而使得AsyncTask的任務得到真正的執行。關于FutureTask的run方法的源碼如下:

public void run() {
    ........
    try {
        Callable<V> c = callable; // 這個callable就是構造方法中傳入的Callable對象,即mWorker
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call(); // 在這里Callable對象的call方法將會被調用
                ran = true;
            } catch (Throwable ex) {
                ........
            }
            if (ran)
                set(result); 
        }
    } finally {
        ........
    }
}

而在實例化mFuture對象時重寫的這個done方法,在FutureTask類中的finishCompletion方法中會被調用,而finishCompletion方法存在三處被調用的地方,其中一處就在剛剛說到的run方法的結尾處調用的set方法內部,關于這個done方法內部調用的postResultIfNotInvoked方法,源碼如下:

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

在前面說到的mWorker的call方法中,mTaskInvoked變量會調用set方法傳入true,此時如果我們調用mTaskInvoked的get方法獲取到的返回值就為true。在AsyncTask中這個mTaskInvoked變量只有這一處調用set方法的地方,因此可以得出一個結論,一旦mWorker的call被調用了,那么在postResultIfNotInvoked方法中就不會執行postResult方法。正常情況下,只要調用了AsyncTask的execute方法開始執行任務,mWorker的call方法就會被正常調用,postResult方法也會在call方法的結尾處調用,而在done方法的內部就不會再次執行postResult方法;而如果當前的任務還未被執行,此時調用了AsyncTask的cancel方法取消任務,這時mWorker的call方法還未被調用,也就是說這時mTaskInvoked通過get方法返回的值為默認值false,那么此時如果mFuture的done方法被調用,postResult方法就會在其內部被調用。而AsyncTask的cancel方法內部其實就是調用mFuture的cancel方法,最終就會導致mFuture的done方法被調用,因此當一個AsyncTask任務如果在執行之前被取消掉,那么它的postResult方法會在mFuture的done方法中被調用。

4.5.execute方法:

在了解了AsyncTask的構造方法的邏輯之后,我們來看一下它的開啟任務執行的execute方法:

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

方法的內部繼續調用executeOnExecutor方法,executeOnExecutor方法接收兩個參數,第一個參數是一個實現了Executor接口類型的參數,第二個參數為execute方法中傳入的最終執行任務所需的參數。在execute方法中傳給executeOnExecutor方法的第一個參數為sDefaultExecutor變量,sDefaultExecutor是AsyncTask類的一個靜態變量,這個變量的類型為SerialExecutor類型,而SerialExecutor也是AsyncTask的一個靜態內部類。關于這個SerialExecutor類后面再做詳細分析,我們先來看一下executeOnExecutor方法的源碼:

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
1    if (mStatus != Status.PENDING) {
2        switch (mStatus) {
3            case RUNNING:
4                throw new IllegalStateException("Cannot execute task:"
5                            + " the task is already running.");
6            case FINISHED:
7                throw new IllegalStateException("Cannot execute task:"
8                            + " the task has already been executed "
9                            + "(a task can be executed only once)");
10        }
11    }
12    mStatus = Status.RUNNING;
13    onPreExecute();
14    mWorker.mParams = params;
15    exec.execute(mFuture);
16    return this;
}

首先,對mStatus 變量的值進行判斷,這個mStatus 是一個枚舉類型的變量,它的值有三種可能:

1.Status.PENDING: 任務尚未執行。
2.Status.RUNNING: 任務正在執行。
3.Status.FINISHED: 任務已經執行完成。

默認情況下mStatus的值為Status.PENDING,在AsyncTask中mStatus存在兩處賦值的地方,一處是前面說到的finish方法中會將其置為Status.FINISHED,另一處就是當前的executeOnExecutor方法的第12行會將其置為Status.RUNNING。
現在回到executeOnExecutor方法,當mStatus的值不等于Status.PENDING時方法就會拋出異常,這也就驗證了AsyncTask的execute方法確實只能調用一次,當mStatus的值為Status.PENDING時,說明此時任務還未被執行,那么就向下執行調用前面說到的onPreExecute方法(注意此時異步任務還未真正開始執行),這個方法本身是一個空方法,如果我們需要在任務正式開始執行前做一些什么準備操作的話可以重寫該方法來編寫適當的邏輯;接著將傳給任務的參數賦值給mWorker的mParams變量,這個mParams最終會在mWorker的call方法中傳給AsyncTask的doInBackground方法;最后在第15行會執行Executor的execute方法來執行當前的mFuture任務了,接下來就要揭曉AsyncTask中的兩個最關鍵的角色了。

4.6.線程池SerialExecutor:

前面我們已經提到過,在AsyncTask的execute方法中傳給executeOnExecutor方法的第一個參數為sDefaultExecutor變量,而sDefaultExecutor是一個SerialExecutor類型的靜態變量,那么我們來看一下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(); // 內部會調用mWorker的call方法去執行doInBackground方法
                } finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }

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

在SerialExecutor的內部,擁有兩個成員變量mTasks和mActive,其中mTasks為范型為Runnable的隊列,用于存儲Runnable對象,在AsyncTask類中就是FutureTask對象。在execute方法中,首先調用ArrayDeque的offer方法將傳入的FutureTask對象添加到mTasks的結尾,然后判斷mActive變量是否為空,如果為空就調用scheduleNext方法,在scheduleNext方法中首先會將mActive指向mTasks中的第一個元素(ArrayDeque的poll方法返回隊列中的第一個元素),如果mActive不為空,就會去調用THREAD_POOL_EXECUTOR線程池的execute方法執行當前的mActive,而在線程池的execute方法中,mActive的run方法最終就會被調用(這一點前面也提到過,需要對線程池和線程的執行原理有一定的了解)。現在再來看下剛剛在調用mTasks的offer方法添加FutureTask對象時重寫了FutureTask的run方法,在run方法中先是執行FutureTask自身的run方法,然后會接著調用scheduleNext方法,如果mTasks中還有FutureTask對象,就繼續執行下一個FutureTask,由此可知AsyncTask中的任務其實是串行執行的。

4.7.THREAD_POOL_EXECUTOR:

到這里其實AsyncTask任務的執行流程就差不多分析完了,只不過還有一個重要角色沒介紹,那就是執行FutureTask的線程池THREAD_POOL_EXECUTOR,它是AsyncTask的一個靜態變量,就是一個正常的ThreadPoolExecutor類型的線程池,它的核心線程數為1,最大線程數為20,這也就意味著當使用默認的THREAD_POOL_EXECUTOR線程池時AsyncTask允許同時執行的任務最多有20個。如果覺著默認的線程池不滿足你的需要,我們也可以通過調用AsyncTask的setDefaultExecutor方法為當前的AsyncTask設置自定義的線程池來執行任務。

總結

AsyncTask的主要知識點以及任務的執行機制基本上就分析完了,在它的內部有幾個重要的角色如下:

1.AsyncTask的內部存在兩個線程池,一個是SerialExecutor類型的sDefaultExecutor,它用來存儲將要被執行的FutureTask任務,使任務按順序串行執行;另一個THREAD_POOL_EXECUTOR為ThreadPoolExecutor類型,它用于執行FutureTask任務。
2.InternalHandler類型的mHandler對象負責處理任務執行過程中的消息傳遞。
3.FutureTask類型的mFuture對象,代表一個將要被THREAD_POOL_EXECUTOR執行的任務。
4.WorkerRunnable類型的mWorker對象,是任務的真正執行者,它被mFuture對象持有。

當調用構造方法創建一個AsyncTask實例時,會在構造方法中創建三個變量,分別為傳遞消息的mHandler、任務的真正執行者mWorker以及將要被執行的任務mFuture,其中mWorker會被傳入到mFuture的內部,當mFuture的run方法被調用時,mWorker的call方法就會被調用,最終doInBackground方法就會被執行,耗時任務就得以執行;當創建完AsyncTask實例后,調用它的execute方法將mFuture添加到sDefaultExecutor中,最終再交給THREAD_POOL_EXECUTOR的execute方法去處理,這時mFuture的run方法就會被調用。至此,整個AsyncTask的工作機制就已經分析完了!最后,如果文章對您有幫助,還希望點贊支持下!

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