手撕 Volley (一)


android_volley_tutorial.jpg

前言

從去年開(kāi)始使用Volley,到現(xiàn)在一年多了强霎。前幾天參加某互聯(lián)網(wǎng)公司校招被問(wèn)到Volley相對(duì)其他的網(wǎng)絡(luò)框架有什么優(yōu)缺點(diǎn),它分別是如何實(shí)現(xiàn)的哼转。當(dāng)時(shí)答得的并不好。所以趁十一假期讀一下Volley的源碼拓春。

寫這篇文章的目的有兩個(gè):1. 總結(jié)下 Android 網(wǎng)絡(luò)編程,學(xué)習(xí) Volley 設(shè)計(jì)思想释簿。2. 給正在使用 Volley 但仍然心存疑惑的人一些更深入的解析。


Volley到底是什么

Volley Github主頁(yè)
Android 網(wǎng)絡(luò)通信框架Volley簡(jiǎn)介(Google IO 2013)

Volley簡(jiǎn)介

volley 是 Goole I/O 2013上發(fā)布的網(wǎng)絡(luò)通信庫(kù)硼莽,使網(wǎng)絡(luò)通信更快、更簡(jiǎn)單煮纵、更健壯懂鸵。
關(guān)鍵詞:數(shù)據(jù)不大但通信頻繁
Volley名稱的由來(lái): a burst or emission of many things or a large amount at once

Volley提供的功能
  • Json,圖像等異步下載
  • 網(wǎng)絡(luò)請(qǐng)求的排序(scheduling)
  • 網(wǎng)絡(luò)請(qǐng)求的優(yōu)先級(jí)處理
  • 緩存
  • 多級(jí)別取消請(qǐng)求
  • 和 Activity 的生命周期聯(lián)動(dòng)(Activity 結(jié)束時(shí)同時(shí)取消所有網(wǎng)絡(luò)請(qǐng)求)

Volley好在哪

HttpClient行疏、HttpURLConnection匆光、OKHttp和Volley優(yōu)缺點(diǎn)和性能對(duì)比

物理質(zhì)量
  • 使用Volley 需要Volley.jar(120k),加上自己的封裝最多140k酿联。
  • 使用OkHttp需要 okio.jar (80k), okhttp.jar(330k)這2個(gè)jar包终息,總大小差不多400k,加上自己的封裝,差不多得410k贞让。
Volley 的優(yōu)點(diǎn)
  • 非常適合進(jìn)行數(shù)據(jù)量不大周崭,但通信頻繁的網(wǎng)絡(luò)操作
  • 可直接在主線程調(diào)用服務(wù)端并處理返回結(jié)果
  • 可以取消請(qǐng)求,容易擴(kuò)展喳张,面向接口編程
  • 網(wǎng)絡(luò)請(qǐng)求線程N(yùn)etworkDispatcher默認(rèn)開(kāi)啟了4個(gè)续镇,可以優(yōu)化,通過(guò)手機(jī)CPU數(shù)量
  • 通過(guò)使用標(biāo)準(zhǔn)的HTTP緩存機(jī)制保持磁盤和內(nèi)存響應(yīng)的一致
Volley 的缺點(diǎn)
  • 使用的是httpclient销部、HttpURLConnection
  • 6.0不支持httpclient了摸航,如果想支持得添加org.apache.http.legacy.jar
  • 對(duì)大文件下載 Volley的表現(xiàn)非常糟糕
  • 只支持http請(qǐng)求
  • 圖片加載性能一般

Volley 的使用場(chǎng)景和使用方式

關(guān)于 Volley 怎么用網(wǎng)絡(luò)上的文章太多了,鏈接整理如下

Android Volley完全解析(一)舅桩,初識(shí)Volley的基本用法
Android Volley完全解析(二)酱虎,使用Volley加載網(wǎng)絡(luò)圖片
Android Volley完全解析(三),定制自己的Request
Android Volley 之自定義Request
官方教程(需要翻墻)
An Introduction to Volley


Http 權(quán)威指南筆記

不是要讀 Volley 的源碼嘛擂涛,怎么又看起了 HTTP 權(quán)威指南读串,原因很簡(jiǎn)單,Volley 源碼里面有很多處理是與 HTTP 協(xié)議息息相關(guān)的歼指,只有了解了協(xié)議才能更深入的理解Volley爹土。Volley里面涉及協(xié)議的地方都會(huì)在注釋中給出協(xié)議文檔的鏈接。

Hypertext Transfer Protocol -- HTTP/1.1
HTTP權(quán)威指南讀書(shū)筆記
HTTP協(xié)議詳解(真的很經(jīng)典)
HTTP協(xié)議詳解

這里簡(jiǎn)單介紹一幾個(gè)概念:


HTTP.png
  • HTTP 協(xié)議屬于應(yīng)用層協(xié)議踩身,他的基礎(chǔ)是 TCP (傳輸層)/ IP (網(wǎng)絡(luò)層)協(xié)議
  • 一個(gè)HTTP事務(wù)由一條請(qǐng)求命令和一個(gè)響應(yīng)結(jié)果組成胀茵。這種通信通過(guò)名為 HTTP 報(bào)文(HTTP message)的格式化數(shù)據(jù)塊進(jìn)行
  • 從Web客戶端發(fā)往Web服務(wù)器的HTTP報(bào)文稱為請(qǐng)求報(bào)文(request message)。從服務(wù)器發(fā)往客戶端的報(bào)文稱為響應(yīng)報(bào)文(reponse message)挟阻,請(qǐng)求報(bào)文和響應(yīng)報(bào)文格式類似琼娘。 HTTP報(bào)文包括以下三部分:
  • start line 起始行
    *請(qǐng)求報(bào)文 包括 method + path + version
    *響應(yīng)報(bào)文 包括 version + status line
  • headers 消息報(bào)頭峭弟,包含了很多鍵值對(duì),兩者之間用冒號(hào)(:)分隔脱拼。首部以一個(gè)空行結(jié)束瞒瘸,這里是我們開(kāi)發(fā)主要會(huì)用到的地方,特別是緩存熄浓。建議大家還是閱讀一下連接中給出的相關(guān)文章情臭。
  • entity / body消息實(shí)體,空行之后就是可選的報(bào)文主體了赌蔑,其中包含了所有類型的數(shù)據(jù)俯在。起始行和首部都是文本形式且都是結(jié)構(gòu)化的,而主體則不同娃惯,主體可以包含任意的二進(jìn)制數(shù)據(jù)(圖片跷乐、視頻、音頻趾浅、軟件程序)愕提。當(dāng)然,主體還可以包含文本皿哨。

下面給出一組請(qǐng)求和響應(yīng)的樣例浅侨。


HTTP_RequestMessageExample.png

HTTP_ResponseMessageExample.png

HttpURLConnection 與 HttpClient

這里了解一下 httpClient 和 HttpURLConnection 的區(qū)別和歷史,并主要學(xué)習(xí)一下 HttpURLConnection 的使用往史,android 社區(qū)現(xiàn)在更推薦使用 HttpURLConnection 來(lái)進(jìn)行網(wǎng)絡(luò)開(kāi)發(fā)仗颈。
需要注意的是 android 6.0 SDK,不再提供 org.apache.http 的支持椎例,所以 6.0 以后要想使用 Volley(HttpClient)需要手動(dòng)配置 gradle 了挨决。

HttpURLConnection(官方文檔,需要翻墻)
Interface HttpClient
A Comparison of java.net.URLConnection and HTTPClient
HttpClient和HttpURLConnection的區(qū)別


Volley 源碼解析

ok 做完了前面的準(zhǔn)備工作終于可以開(kāi)始最激動(dòng)人心的部分了订歪,首先來(lái)一張官方給出的流程圖脖祈。

volley-request.png

對(duì)于這張圖我們只需要知道:

  • Volley 運(yùn)行的過(guò)程中一共有三種線程,包括 UI 線程刷晋、Cache 調(diào)度線程和 NetWork 調(diào)度線程池
  • 請(qǐng)求加入優(yōu)先級(jí)隊(duì)列盖高,Cache 線程進(jìn)行篩選,如果命中(hit)分發(fā)給 UI 線程
  • 未命中(miss)交給 NetWork 調(diào)度線程池處理眼虱,取回后更新 Cache 并分發(fā)給 UI 線程
  • 每次請(qǐng)求執(zhí)行過(guò)程始于 UI 線程喻奥, 終于 UI 線程

再來(lái)一張總體設(shè)計(jì)圖:


flow.png
入口

我用的 Sublime Text 3 來(lái)閱讀 Volley ,可以看到 Volley 中大大小小一共43個(gè)類捏悬。
我們使用 Volley 的第一步是通過(guò)Volley 的 newRequestQueue 方法得到 一個(gè)RequestQueue 隊(duì)列撞蚕。那么我們就從這個(gè)方法開(kāi)始吧。不管幾個(gè)參數(shù)的 newRequestQueue 方法最終都會(huì)調(diào)用下面這個(gè)三個(gè)參數(shù)的过牙。

QQ截圖20161004202730.png

可以看到:

  1. 在磁盤上創(chuàng)建一塊文件
  2. 設(shè)置 UserAgent 甥厦,不知道什么是UserAgent去看前面的HTTP協(xié)議
  3. 根據(jù) SDK 版本的不同初始化 HTTPStack
  4. 用 HTTPStack 初始化 BasicNetwork
  5. 用第一步創(chuàng)建的文件初始化磁盤緩存
  6. 用磁盤緩存和 NetWork 創(chuàng)建我們的 請(qǐng)求隊(duì)列 RequestQueue
  7. 調(diào)用 RequestQueue 的 start 方法

這里面我們有幾個(gè)疑問(wèn)

  • HttpStack纺铭、HurlStack、HttpClientStack 分別是啥
  • NetWork刀疙、BasicNetwork 分別是啥
  • DiskBasedCache 是啥
  • RequestQueue 是啥舶赔,他的 start 方法做了什么事情

首先看第一個(gè) HttpStack

/**
 * An HTTP stack abstraction.
 */
public interface HttpStack {
    /**
     * Performs an HTTP request with the given parameters.
      */
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}
/**
 * An HttpStack that performs request over an {@link HttpClient}.
 */
public class HttpClientStack implements HttpStack {
/**
 * An {@link HttpStack} based on {@link HttpURLConnection}.
 */
public class HurlStack implements HttpStack {

HttpStack 類圖


HttpStack.png

很明顯 HttpStack 是一個(gè)接口并且只有一個(gè) performRequest 方法。
而 HurlStack 和 HttpClientStack 分別是基于 HttpUrlConnection 和 HttpClient 對(duì) HttpStack 的實(shí)現(xiàn)谦秧,是真正用來(lái)訪問(wèn)網(wǎng)絡(luò)的類竟纳。內(nèi)部具體實(shí)現(xiàn)后面再看。
下面再來(lái)看NetWork 和 BasicNetWork:

/**
 * An interface for performing requests.
 */
public interface Network {
    /**
     * Performs the specified request.
     * @param request Request to process
     * @return A {@link NetworkResponse} with data and caching metadata; will never be null
     * @throws VolleyError on errors
     */
    public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}


}
/**
 * A network performing Volley requests over an {@link HttpStack}.
 */
public class BasicNetwork implements Network {
    protected static final boolean DEBUG = VolleyLog.DEBUG;

    private static int SLOW_REQUEST_THRESHOLD_MS = 3000;

    private static int DEFAULT_POOL_SIZE = 4096;

    protected final HttpStack mHttpStack;

    protected final ByteArrayPool mPool;

    /**
     * @param httpStack HTTP stack to be used
     */
    public BasicNetwork(HttpStack httpStack) {
        // If a pool isn't passed in, then build a small default pool that will give us a lot of
        // benefit and not use too much memory.
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    /**
     * @param httpStack HTTP stack to be used
     * @param pool a buffer pool that improves GC performance in copy operations
     */
    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        mHttpStack = httpStack;
        mPool = pool;
    }



}
Network.png

可以看到同樣是接口與實(shí)現(xiàn)類的關(guān)系油够,內(nèi)部封裝了一個(gè) HttpStack 用來(lái)是想網(wǎng)絡(luò)請(qǐng)求蚁袭。
接口的方法是一樣的,這又是為什么呢石咬,這里暫且不管,后面看實(shí)現(xiàn)再分析卖哎。
接下來(lái)是 DiskBasedCache:

/**
 * Cache implementation that caches files directly onto the hard disk in the specified
 * directory. The default disk usage size is 5MB, but is configurable.
 */
public class DiskBasedCache implements Cache {

可以看得到 DiskBaseCache 繼承自 Cache接口鬼悠,老規(guī)矩我們先看接口不看具體實(shí)現(xiàn),先知道他是干嘛的亏娜。


/**
 * An interface for a cache keyed by a String with a byte array as data.
 */
public interface Cache {
    /**
     *  an entry from the cache.
     * @param key Cache key
     * @return An {@link Entry} or null in the event of a cache miss
     */
    public Entry get(String key);
    public void put(String key, Entry entry)焕窝;
    public void initialize();  
    public void invalidate(String key, boolean fullExpire);
    public void remove(String key);
    public void clear();

    /**
     * Data and metadata for an entry returned by the cache.
     */
    public static class Entry {
        /** The data returned from cache. */
        public byte[] data;

        /** ETag for cache coherency. */
        public String etag;

        /** Date of this response as reported by the server. */
        public long serverDate;

        /** The last modified date for the requested object. */
        public long lastModified;

        /** TTL for this record. */
        public long ttl;

        /** Soft TTL for this record. */
        public long softTtl;

        /** Immutable response headers as received from server; must be non-null. */
        public Map<String, String> responseHeaders = Collections.emptyMap();

        /** True if the entry is expired. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }

}

Cache.png

又是接口维贺,Volley 核心功能的實(shí)現(xiàn)都是基于接口的它掂。
我們來(lái)看,接口 Cache 里面封裝了一個(gè)靜態(tài)內(nèi)部類 Entry(登記)溯泣,這個(gè)內(nèi)部類非常重要虐秋,看了 HTTP 協(xié)議的同學(xué)們會(huì)發(fā)現(xiàn),Entry 里面定義的這些成員變量跟 headers(消息報(bào)頭)里面關(guān)于緩存的標(biāo)簽是一樣的垃沦,這也是前面強(qiáng)調(diào)要看協(xié)議的原因客给。其中還維護(hù)了一個(gè)map 用來(lái)保存消息報(bào)頭中的 key / value,data 來(lái)保存 entity 消息實(shí)體肢簿。除此之外就是一些集合操作了靶剑。
我們使用 Volley 的時(shí)候創(chuàng)建一個(gè) request 然后把它丟到 RequestQueue 中就可以了。那么來(lái)看 RequestQueue 的構(gòu)造方法池充,下面是最終會(huì)調(diào)用的構(gòu)造器桩引。

public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

設(shè)置了幾個(gè)成員變量,那么 RequestQueue到底有哪些成員變量呢

/** Used for generating monotonically-increasing sequence numbers for requests. */
    private AtomicInteger mSequenceGenerator = new AtomicInteger();

    /**
     * Staging area for requests that already have a duplicate request in flight.
     *
     * <ul>
     *     <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache
     *          key.</li>
     *     <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request
     *          is <em>not</em> contained in that list. Is null if no requests are staged.</li>
     * </ul>
     */
    private final Map<String, Queue<Request<?>>> mWaitingRequests =
            new HashMap<String, Queue<Request<?>>>();

    /**
     * The set of all requests currently being processed by this RequestQueue. A Request
     * will be in this set if it is waiting in any queue or currently being processed by
     * any dispatcher.
     */
    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

    /** The cache triage queue. */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

    /** The queue of requests that are actually going out to the network. */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();

    /** Number of network request dispatcher threads to start. */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    /** Cache interface for retrieving and storing responses. */
    private final Cache mCache;

    /** Network interface for performing requests. */
    private final Network mNetwork;

    /** Response delivery mechanism. */
    private final ResponseDelivery mDelivery;

    /** The network dispatchers. */
    private NetworkDispatcher[] mDispatchers;

    /** The cache dispatcher. */
    private CacheDispatcher mCacheDispatcher;

    private List<RequestFinishedListener> mFinishedListeners =
            new ArrayList<RequestFinishedListener>();

所有的成員變量以及核心方法類圖如下收夸,為了直觀方法沒(méi)有加參數(shù):


RequestQueue.png
  • mSequenceGenerator:序列號(hào)生成器
  • mWaitingRequests:hashmap 通過(guò) method + url 為key坑匠,重復(fù) request 組成的 queue 為value
  • mCurrentRequests:HashSet 存儲(chǔ)包括正在執(zhí)行和等待所有的 request
  • mCacheQueue:PriorityBlockingQueue 緩存隊(duì)列
  • mNetworkQueue:PriorityBlockingQueue 網(wǎng)絡(luò)請(qǐng)求隊(duì)列
  • DEFAULT_NETWORK_THREAD_POOL_SIZE 網(wǎng)絡(luò)請(qǐng)求線程池大小
  • mCache 接口 具體實(shí)現(xiàn)由構(gòu)造器傳入
  • mNetwork 同上
  • mDelivery 結(jié)果分發(fā)器
  • mDispatchers 網(wǎng)絡(luò)調(diào)度數(shù)組
  • mCacheDispatcher 緩存調(diào)度
    RequestQueue 中一共有五個(gè)主要的方法,分別是 start咱圆、add笛辟、stop功氨、cancel、finish
    我們先看 剛才遇到的 start 方法中都做了些什么
 /**
     * Starts the dispatchers in this queue.
     */
    public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

先調(diào)用了 stop手幢,然后分別調(diào)用了dispatcher 的 start

  /**
     * Stops the cache and network dispatchers.
     */
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }

stop 調(diào)用了分別調(diào)用了 dispatcher 的quit
那么疑問(wèn)來(lái)了捷凄,dispatcher 的 quit 和 start 是干嘛呢

 public class CacheDispatcher extends Thread {
 public class NetworkDispatcher extends Thread {

CacheDispatcher 和 NetworkDispatcher 都繼承自Thread,start 方法自然是開(kāi)啟一個(gè)新的線程那quit围来,一定是關(guān)閉線程了跺涤,看一下 Volley 是怎么實(shí)現(xiàn)的

  public void quit() {
        mQuit = true;
        interrupt();
    }
   @Override
    public void run() {
        while (true) {
                if (mQuit) {
                    return;
                }
        }
    }

我們忽略具體實(shí)現(xiàn)可以看到,run 方法里面是一個(gè) while true 的無(wú)限循環(huán)监透,然后用以個(gè)標(biāo)記字段桶错,來(lái)控制循環(huán)退出。
所以 start 方法做的的事情就很清楚了胀蛮,先 stop 掉跑著的線程院刁,然后開(kāi)啟一個(gè)緩存線程, 一組(默認(rèn)四個(gè))網(wǎng)絡(luò)線程粪狼,每個(gè)里面都有一個(gè)while ture 死循環(huán)退腥。等待 request add 到 Requestqueue 中,接下來(lái)我們就來(lái)看五個(gè)主要方法中的 add
手撕 Volley(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末再榄,一起剝皮案震驚了整個(gè)濱河市狡刘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌困鸥,老刑警劉巖嗅蔬,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異疾就,居然都是意外死亡澜术,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門虐译,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瘪板,“玉大人,你說(shuō)我怎么就攤上這事漆诽∥昱剩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵厢拭,是天一觀的道長(zhǎng)兰英。 經(jīng)常有香客問(wèn)我,道長(zhǎng)供鸠,這世上最難降的妖魔是什么畦贸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上薄坏,老公的妹妹穿的比我還像新娘趋厉。我一直安慰自己,他們只是感情好胶坠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布君账。 她就那樣靜靜地躺著,像睡著了一般沈善。 火紅的嫁衣襯著肌膚如雪乡数。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天闻牡,我揣著相機(jī)與錄音净赴,去河邊找鬼。 笑死罩润,一個(gè)胖子當(dāng)著我的面吹牛玖翅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播割以,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼烧栋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拳球?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤珍特,失蹤者是張志新(化名)和其女友劉穎祝峻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扎筒,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡莱找,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嗜桌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奥溺。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖骨宠,靈堂內(nèi)的尸體忽然破棺而出浮定,到底是詐尸還是另有隱情,我是刑警寧澤层亿,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布桦卒,位于F島的核電站,受9級(jí)特大地震影響匿又,放射性物質(zhì)發(fā)生泄漏方灾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望裕偿。 院中可真熱鬧洞慎,春花似錦、人聲如沸嘿棘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蔫巩。三九已至谆棱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間圆仔,已是汗流浹背垃瞧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坪郭,地道東北人个从。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像歪沃,于是被迫代替她去往敵國(guó)和親嗦锐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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