Android多線程:AsyncTask的原理及其源碼分析

前言

  • AsyncTaskAndroid開發(fā)中是十分常見的
  • 今天,我將全面講解AsyncTask的源碼旨指,希望你們會喜歡

Carson帶你學多線程系列
基礎匯總
Android多線程:基礎知識匯總
基礎使用
Android多線程:繼承Thread類使用(含實例教程)
Android多線程:實現(xiàn)Runnable接口使用(含實例教程)
復合使用
Android 多線程:AsyncTask使用教程(含實例講解)
Android 多線程:AsyncTask原理及源碼分析
Android多線程:HandlerThread使用教程(含實例講解)
Android多線程:HandlerThread原理及源碼分析
Android多線程:IntentService使用教程(含實例講解)
Android多線程:IntentService的原理及源碼分析
Android多線程:線程池ThreadPool全方位教學
相關使用
Android異步通信:這是一份全面&詳細的Handler機制學習攻略
Android多線程:手把手教你全面學習神秘的Synchronized關鍵字
Android多線程:帶你了解神秘的線程變量 ThreadLocal


目錄

示意圖

1. 簡介

示意圖

更詳細了解厦取,請看文章:Android 多線程:AsyncTask最詳細使用教程

本文主要講解其工作原理 & 源碼分析


2. 工作原理

2.1 儲備知識:線程

  • 簡介
示意圖
  • 與進程的區(qū)別
示意圖

2.2 具體原理介紹

  • AsyncTask的實現(xiàn)原理 = 線程池 + Handler

其中:線程池用于線程調(diào)度憔杨、復用 & 執(zhí)行任務;Handler 用于異步通信

  • 其內(nèi)部封裝了2個線程池 + 1個Handler蒜胖,具體介紹如下:
示意圖

3. 類 & 方法介紹

在進行源碼分析前消别,先介紹AsyncTask中的類 & 核心方法

3.1 類定義

AsyncTask類屬于抽象類,即使用時需 實現(xiàn)子類

public abstract class AsyncTask<Params, Progress, Result> { 
 ... 
}

// 類中參數(shù)為3種泛型類型
// 整體作用:控制AsyncTask子類執(zhí)行線程任務時各個階段的返回類型
// 具體說明:
    // a. Params:開始異步任務執(zhí)行時傳入的參數(shù)類型台谢,對應excute()中傳遞的參數(shù)
    // b. Progress:異步任務執(zhí)行過程中寻狂,返回下載進度值的類型
    // c. Result:異步任務執(zhí)行完成后,返回的結(jié)果類型朋沮,與doInBackground()的返回值類型保持一致
// 注:
    // a. 使用時并不是所有類型都被使用
    // b. 若無被使用蛇券,可用java.lang.Void類型代替
    // c. 若有不同業(yè)務,需額外再寫1個AsyncTask的子類
}

3.2 核心方法

  • AsyncTask 核心 & 常用的方法如下:
示意圖
  • 方法執(zhí)行順序如下
示意圖

4. 源碼分析

  • 本次源碼分析將根據(jù) AsyncTask的使用步驟講解

若不熟悉樊拓,請務必看文章:Android 多線程:AsyncTask最詳細使用教程

  • AsyncTask的使用步驟有3個:
  1. 創(chuàng)建 AsyncTask 子類 & 根據(jù)需求實現(xiàn)核心方法
  2. 創(chuàng)建 AsyncTask子類的實例對象(即 任務實例)
  3. 手動調(diào)用execute(()從而執(zhí)行異步線程任務
  • 具體介紹如下
/**
  * 步驟1:創(chuàng)建AsyncTask子類
  * 注: 
  *   a. 繼承AsyncTask類
  *   b. 為3個泛型參數(shù)指定類型纠亚;若不使用,可用java.lang.Void類型代替
  *   c. 根據(jù)需求筋夏,在AsyncTask子類內(nèi)實現(xiàn)核心方法
  */

  private class MyTask extends AsyncTask<Params, Progress, Result> {

        ....

      // 方法1:onPreExecute()
      // 作用:執(zhí)行 線程任務前的操作
      // 注:根據(jù)需求復寫
      @Override
      protected void onPreExecute() {
           ...
        }

      // 方法2:doInBackground()
      // 作用:接收輸入?yún)?shù)蒂胞、執(zhí)行任務中的耗時操作、返回 線程任務執(zhí)行的結(jié)果
      // 注:必須復寫条篷,從而自定義線程任務
      @Override
      protected String doInBackground(String... params) {

            ...// 自定義的線程任務

            // 可調(diào)用publishProgress()顯示進度, 之后將執(zhí)行onProgressUpdate()
             publishProgress(count);
              
         }

      // 方法3:onProgressUpdate()
      // 作用:在主線程 顯示線程任務執(zhí)行的進度
      // 注:根據(jù)需求復寫
      @Override
      protected void onProgressUpdate(Integer... progresses) {
            ...

        }

      // 方法4:onPostExecute()
      // 作用:接收線程任務執(zhí)行結(jié)果骗随、將執(zhí)行結(jié)果顯示到UI組件
      // 注:必須復寫蛤织,從而自定義UI操作
      @Override
      protected void onPostExecute(String result) {

         ...// UI操作

        }

      // 方法5:onCancelled()
      // 作用:將異步任務設置為:取消狀態(tài)
      @Override
        protected void onCancelled() {
        ...
        }
  }

/**
  * 步驟2:創(chuàng)建AsyncTask子類的實例對象(即 任務實例)
  * 注:AsyncTask子類的實例必須在UI線程中創(chuàng)建
  */
  MyTask mTask = new MyTask();

/**
  * 步驟3:手動調(diào)用execute(Params... params) 從而執(zhí)行異步線程任務
  * 注:
  *    a. 必須在UI線程中調(diào)用
  *    b. 同一個AsyncTask實例對象只能執(zhí)行1次,若執(zhí)行第2次將會拋出異常
  *    c. 執(zhí)行任務中鸿染,系統(tǒng)會自動調(diào)用AsyncTask的一系列方法:onPreExecute() 指蚜、doInBackground()、onProgressUpdate() 涨椒、onPostExecute() 
  *    d. 不能手動調(diào)用上述方法
  */
  mTask.execute()摊鸡;

  • 下面,我將根據(jù)上述使用步驟進行源碼分析

步驟1:創(chuàng)建AsyncTask子類

在該步驟中蚕冬,只需知道 “該類中復寫的方法將在后續(xù)源碼中調(diào)用” 即可

步驟2:創(chuàng)建AsyncTask子類的實例對象(即 任務實例)

/**
  * 具體使用
  */
  MyTask mTask = new MyTask();

/**
  * 源碼分析:AsyncTask的構造函數(shù)
  */
  public AsyncTask() {
        // 1. 初始化WorkerRunnable變量 = 一個可存儲參數(shù)的Callable對象 ->>分析1
        mWorker = new WorkerRunnable<Params, Result>() {
            // 在任務執(zhí)行線程池中回調(diào):THREAD_POOL_EXECUTOR.execute()
            // 下面會詳細講解
            public Result call() throws Exception {

                // 添加線程的調(diào)用標識
                mTaskInvoked.set(true); 

                Result result = null;
                try {
                    // 設置線程的優(yōu)先級
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    
                    // 執(zhí)行異步操作 = 耗時操作
                    // 即 我們使用過程中復寫的耗時任務
                    result = doInBackground(mParams);

                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    
                    mCancelled.set(true);// 若運行異常,設置取消的標志
                    throw tr;
                } finally {
                    
                    // 把異步操作執(zhí)行的結(jié)果發(fā)送到主線程
                    // 從而更新UI柱宦,下面會詳細講解
                    postResult(result); 
                }
                return result;
            }
        };

        // 2. 初始化FutureTask變量 = 1個FutureTask ->>分析2
        mFuture = new FutureTask<Result>(mWorker) {

            // done()簡介:FutureTask內(nèi)的Callable執(zhí)行完后的調(diào)用方法
            // 作用:復查任務的調(diào)用、將未被調(diào)用的任務的結(jié)果通過InternalHandler傳遞到UI線程
            @Override
            protected void done() {
                try {

                    // 在執(zhí)行完任務后檢查,將沒被調(diào)用的Result也一并發(fā)出 ->>分析3
                    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) {

                    //若 發(fā)生異常,則將發(fā)出null
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

/**
  * 分析1:WorkerRunnable類的構造函數(shù)
  */
  private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        // 此處的Callable也是任務播瞳;
        // 與Runnable的區(qū)別:Callable<T>存在返回值 = 其泛型
        Params[] mParams;
    }

/**
  * 分析2:FutureTask類的構造函數(shù)
  * 定義:1個包裝任務的包裝類
  * 注:內(nèi)部包含Callable<T> 掸刊、增加了一些狀態(tài)標識 & 操作Callable<T>的接口
  */
  public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;      
    }
    // 回到調(diào)用原處

/**
  * 分析3:postResultIfNotInvoked()
  */
  private void postResultIfNotInvoked()(Result result) {
        // 取得任務標記
        final boolean wasTaskInvoked = mTaskInvoked.get();

        // 若任務無被執(zhí)行,將未被調(diào)用的任務的結(jié)果通過InternalHandler傳遞到UI線程
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }

總結(jié):

  • 創(chuàng)建了1個WorkerRunnable類 的實例對象 & 復寫了call()方法
  • 創(chuàng)建了1個FutureTask類 的實例對象 & 復寫了 done()

步驟3:手動調(diào)用execute(Params... params)

/**
  * 具體使用
  */
  mTask.execute();

/**
  * 源碼分析:AsyncTask的execute()
  */
  public final AsyncTask<Params, Progress, Result> execute(Params... params) {

        return executeOnExecutor(sDefaultExecutor, params);
        // ->>分析1

    }

 /**
  * 分析1:executeOnExecutor(sDefaultExecutor, params)
  * 參數(shù)說明:sDefaultExecutor = 任務隊列 線程池類(SerialExecutor)的對象
  */
  public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {

        // 1. 判斷 AsyncTask 當前的執(zhí)行狀態(tài)
        // PENDING = 初始化狀態(tài)
        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)");
            }
        }

        // 2. 將AsyncTask狀態(tài)設置為RUNNING狀態(tài)
        mStatus = Status.RUNNING;

        // 3. 主線程初始化工作
        onPreExecute();

        // 4. 添加參數(shù)到任務中
        mWorker.mParams = params;

        // 5. 執(zhí)行任務
        // 此處的exec = sDefaultExecutor = 任務隊列 線程池類(SerialExecutor)的對象
        // ->>分析2
        exec.execute(mFuture);
        return this;
    }

/**
  * 分析2:exec.execute(mFuture)
  * 說明:屬于任務隊列 線程池類(SerialExecutor)的方法
  */
  private static class SerialExecutor implements Executor {
        // SerialExecutor = 靜態(tài)內(nèi)部類
        // 即 是所有實例化的AsyncTask對象公有的

        // SerialExecutor 內(nèi)部維持了1個雙向隊列赢乓;
        // 容量根據(jù)元素數(shù)量調(diào)節(jié)
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        // execute()被同步鎖synchronized修飾
        // 即說明:通過鎖使得該隊列保證AsyncTask中的任務是串行執(zhí)行的
        // 即 多個任務需1個個加到該隊列中忧侧;然后 執(zhí)行完隊列頭部的再執(zhí)行下一個,以此類推
        public synchronized void execute(final Runnable r) {
            // 將實例化后的FutureTask類 的實例對象傳入
            // 即相當于:向隊列中加入一個新的任務
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();->>分析3
                    }
                }
            });
            // 若當前無任務執(zhí)行牌芋,則去隊列中取出1個執(zhí)行
            if (mActive == null) {
                scheduleNext();
            }
        }
        // 分析3
        protected synchronized void scheduleNext() {
            // 1. 取出隊列頭部任務
            if ((mActive = mTasks.poll()) != null) {

                // 2. 執(zhí)行取出的隊列頭部任務
                // 即 調(diào)用執(zhí)行任務線程池類(THREAD_POOL_EXECUTOR)->>繼續(xù)往下看
                THREAD_POOL_EXECUTOR.execute(mActive);
                
            }
        }
    }

總結(jié):

  • 執(zhí)行任務前蚓炬,通過 任務隊列 線程池類(SerialExecutor)將任務按順序放入到隊列中;

通過同步鎖 修飾execute()從而保證AsyncTask中的任務是串行執(zhí)行的

  • 之后的線程任務執(zhí)行是 通過任務線程池類(THREAD_POOL_EXECUTOR) 進行的躺屁。

繼續(xù)往下分析:THREAD_POOL_EXECUTOR.execute()

/**
  * 源碼分析:THREAD_POOL_EXECUTOR.execute()
  * 說明:
  *     a. THREAD_POOL_EXECUTOR實際上是1個已配置好的可執(zhí)行并行任務的線程池
  *     b. 調(diào)用THREAD_POOL_EXECUTOR.execute()實際上是調(diào)用線程池的execute()去執(zhí)行具體耗時任務
  *     c. 而該耗時任務則是步驟2中初始化WorkerRunnable實例對象時復寫的call()
  * 注:下面先看任務執(zhí)行線程池的線程配置過程肯夏,看完后請回到步驟2中的源碼分析call()
  */

    // 步驟1:參數(shù)設置
        //獲得當前CPU的核心數(shù)
        private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        //設置線程池的核心線程數(shù)2-4之間,但是取決于CPU核數(shù)
        private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
        //設置線程池的最大線程數(shù)為 CPU核數(shù)*2+1
        private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
        //設置線程池空閑線程存活時間30s
        private static final int KEEP_ALIVE_SECONDS = 30;

        //初始化線程工廠
        private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);

            public Thread newThread(Runnable r) {
                return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
            }
        };

        //初始化存儲任務的隊列為LinkedBlockingQueue 最大容量為128
        private static final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);

    // 步驟2: 根據(jù)參數(shù)配置執(zhí)行任務線程池,即 THREAD_POOL_EXECUTOR
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        // 設置核心線程池的 超時時間也為30s
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    // 請回到步驟2中的源碼分析call()

至此犀暑,我們回到步驟2中的源碼分析call()

/**
  * 步驟2的源碼分析:AsyncTask的構造函數(shù)
  */
    public AsyncTask() {
        // 1. 初始化WorkerRunnable變量 = 一個可存儲參數(shù)的Callable對象
        mWorker = new WorkerRunnable<Params, Result>() {

            public Result call() throws Exception {

                // 添加線程的調(diào)用標識
                mTaskInvoked.set(true); 

                Result result = null;
                try {
                    // 設置線程的優(yōu)先級
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    
                    // 執(zhí)行異步操作 = 耗時操作
                    // 即 我們使用過程中復寫的耗時任務
                    result = doInBackground(mParams);

                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    
                    mCancelled.set(true);// 若運行異常,設置取消的標志
                    throw tr;
                } finally {
                    
                    // 把異步操作執(zhí)行的結(jié)果發(fā)送到主線程
                    // 從而更新UI ->>分析1
                    postResult(result); 
                }
                return result;
            }
        };

        .....// 省略
    }
/**
  * 分析1:postResult(result)
  */
   private Result postResult(Result result) {

        @SuppressWarnings("unchecked")

        // 創(chuàng)建Handler對象 ->> 源自InternalHandler類—>>分析2
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        // 發(fā)送消息到Handler中
        message.sendToTarget();
        return result;

    }


/**
  * 分析2:InternalHandler類
  */
    private static class InternalHandler extends Handler {

        // 構造函數(shù)
        public InternalHandler() {
            super(Looper.getMainLooper());
            // 獲取的是主線程的Looper()
            // 故 AsyncTask的實例創(chuàng)建 & execute()必須在主線程使用
        }

        @Override
        public void handleMessage(Message msg) {

            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;

            switch (msg.what) {
                // 若收到的消息 = MESSAGE_POST_RESULT
                // 則通過finish() 將結(jié)果通過Handler傳遞到主線程
                case MESSAGE_POST_RESULT:
                    result.mTask.finish(result.mData[0]); ->>分析3
                    break;

                // 若收到的消息 = MESSAGE_POST_PROGRESS
                // 則回調(diào)onProgressUpdate()通知主線程更新進度的操作
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
/**
  * 分析3:result.mTask.finish(result.mData[0])
  */
  private void finish(Result result) {
        // 先判斷是否調(diào)用了Cancelled()
            // 1. 若調(diào)用了則執(zhí)行我們復寫的onCancelled()
            // 即 取消任務時的操作
            if (isCancelled()) {
                onCancelled(result);
            } else {

            // 2. 若無調(diào)用Cancelled()驯击,則執(zhí)行我們復寫的onPostExecute(result)
            // 即更新UI操作
                onPostExecute(result);
            }
            // 注:不管AsyncTask是否被取消,都會將AsyncTask的狀態(tài)變更為:FINISHED
            mStatus = Status.FINISHED;
        }

總結(jié)

  • 任務線程池類(THREAD_POOL_EXECUTOR)實際上是1個已配置好的可執(zhí)行并行任務的線程池
  • 調(diào)用THREAD_POOL_EXECUTOR.execute()實際上是調(diào)用線程池的execute()去執(zhí)行具體耗時任務
  • 而該耗時任務則是步驟2中初始化 WorkerRunnable實例對象時復寫的call()內(nèi)容
  • call()方法里耐亏,先調(diào)用 我們復寫的doInBackground(mParams)執(zhí)行耗時操作
  • 再調(diào)用postResult(result)徊都, 通過 InternalHandler 類 將任務消息傳遞到主線程;根據(jù)消息標識(MESSAGE_POST_RESULT)判斷广辰,最終通過finish()調(diào)用我們復寫的onPostExecute(result)暇矫,從而實現(xiàn)UI更新操作

至此,關于AsyncTask的源碼 分析完畢择吊,附上一份最終總結(jié):

示意圖

9. 總結(jié)

  • 本文介紹了多線程中的AsyncTask的 工作原理 & 源碼分析李根,總結(jié)如下:
示意圖
示意圖
  • 下一篇文章我將對講解Android多線程的相關知識,感興趣的同學可以繼續(xù)關注Carson_Ho的簡書

Carson帶你學多線程系列
基礎匯總
Android多線程:基礎知識匯總
基礎使用
Android多線程:繼承Thread類使用(含實例教程)
Android多線程:實現(xiàn)Runnable接口使用(含實例教程)
復合使用
Android 多線程:AsyncTask使用教程(含實例講解)
Android 多線程:AsyncTask原理及源碼分析
Android多線程:HandlerThread使用教程(含實例講解)
Android多線程:HandlerThread原理及源碼分析
Android多線程:IntentService使用教程(含實例講解)
Android多線程:IntentService的原理及源碼分析
Android多線程:線程池ThreadPool全方位教學
相關使用
Android異步通信:這是一份全面&詳細的Handler機制學習攻略
Android多線程:手把手教你全面學習神秘的Synchronized關鍵字
Android多線程:帶你了解神秘的線程變量 ThreadLocal


歡迎關注Carson_Ho的簡書

不定期分享關于安卓開發(fā)的干貨几睛,追求短房轿、平、快,但卻不缺深度冀续。


請點贊琼讽!因為你的鼓勵是我寫作的最大動力必峰!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洪唐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吼蚁,更是在濱河造成了極大的恐慌凭需,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肝匆,死亡現(xiàn)場離奇詭異粒蜈,居然都是意外死亡,警方通過查閱死者的電腦和手機旗国,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門枯怖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人能曾,你說我怎么就攤上這事度硝。” “怎么了寿冕?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵蕊程,是天一觀的道長。 經(jīng)常有香客問我驼唱,道長藻茂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任玫恳,我火速辦了婚禮辨赐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘京办。我一直安慰自己肖油,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布臂港。 她就那樣靜靜地躺著森枪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪审孽。 梳的紋絲不亂的頭發(fā)上县袱,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音佑力,去河邊找鬼式散。 笑死,一個胖子當著我的面吹牛打颤,可吹牛的內(nèi)容都是我干的暴拄。 我是一名探鬼主播漓滔,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乖篷!你這毒婦竟也來了响驴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤撕蔼,失蹤者是張志新(化名)和其女友劉穎豁鲤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲸沮,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡琳骡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了讼溺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楣号。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖怒坯,靈堂內(nèi)的尸體忽然破棺而出炫狱,到底是詐尸還是另有隱情,我是刑警寧澤敬肚,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布毕荐,位于F島的核電站,受9級特大地震影響艳馒,放射性物質(zhì)發(fā)生泄漏憎亚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一弄慰、第九天 我趴在偏房一處隱蔽的房頂上張望第美。 院中可真熱鬧,春花似錦陆爽、人聲如沸什往。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽别威。三九已至,卻和暖如春驴剔,著一層夾襖步出監(jiān)牢的瞬間省古,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工丧失, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留豺妓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像琳拭,于是被迫代替她去往敵國和親训堆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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