源碼學(xué)習(xí)|Volley圖片加載源碼分析

本文將基于Android N Framework層中的Volley庫,對(duì)Volley中的圖片加載框架的源碼進(jìn)行分析

我們?cè)谏弦黄幸呀?jīng)對(duì)Volley的網(wǎng)絡(luò)庫工作流程做了進(jìn)行了簡(jiǎn)單分析,如果有不了解的朋友可以通過下面的鏈接進(jìn)行點(diǎn)贊匀归、關(guān)注和打賞友题。: )

Volley基于基礎(chǔ)的網(wǎng)絡(luò)請(qǐng)求框架封裝了自己的圖片請(qǐng)求框架,Volley中的圖片加載方式總結(jié)起來有三種冀惭,分別為ImageRequest、ImageLoader滑蚯、NetworkImageView丁稀,這三種方式名稱和使用方式各不相同,我們將通過每種加載方式的使用來對(duì)Volley的圖片的加載框架進(jìn)行分析但校。

Volley 圖片請(qǐng)求加載的三種方式
  1. 使用ImageRequest方式加載,其請(qǐng)求示例代碼如下:

     RequestQueue mQueue = Volley.newRequestQueue(context);
     ImageRequest imageRequest = new ImageRequest(url,
         new Response.Listener<Bitmap>() {  
                         @Override  
                         public void onResponse(Bitmap response) {  
                             imageView.setImageBitmap(response);  
                         }  
                     }, 0, 0, Config.RGB_565, new Response.ErrorListener() {  
                         @Override  
                         public void onErrorResponse(VolleyError error) {  
                             imageView.setImageResource(R.drawable.default_image);  
                         }  
                     });  
     mQueue.add(imageRequest);
    
  2. 使用ImageLoader方式加載,其請(qǐng)求示例代碼如下:

       RequestQueue mQueue = Volley.newRequestQueue(context);
       ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {  
           @Override  
           public void putBitmap(String url, Bitmap bitmap) {  
           }  
    
           @Override  
           public Bitmap getBitmap(String url) {  
               return null;  
           }  
       });  
       ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.default_image, R.drawable.failed_image);
       imageLoader.get(url, listener);
    
  3. 使用NetworkImageView方式加載,其請(qǐng)求示例代碼如下:

     <com.android.volley.toolbox.NetworkImageView   
         android:id="@+id/network_image_view"  
         android:layout_width="200dp"  
         android:layout_height="200dp"  
         android:layout_gravity="center_horizontal"  
         />  
    
     RequestQueue mQueue = Volley.newRequestQueue(context);
     ImageLoader imageLoader = new ImageLoader(mQueue,ImageCache);
     networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);  
     networkImageView.setDefaultImageResId(R.drawable.default_image);  
     networkImageView.setErrorImageResId(R.drawable.failed_image);  
     networkImageView.setImageUrl(url, imageLoader);  
    
Volley ImageRequest 請(qǐng)求分析

方式1中ImageRequest的使用方式還是最常用的Request的使用方式,區(qū)別在于在deliverResponse的()結(jié)果不同(具體請(qǐng)求邏輯請(qǐng)參考文章頭部的鏈接文章)。Volley的網(wǎng)絡(luò)請(qǐng)求結(jié)果,如果緩存命中,則會(huì)調(diào)用如下方法對(duì)結(jié)果進(jìn)行解析:

    // We have a cache hit; parse its data for delivery back to the request.
    request.addMarker("cache-hit");
    Response<?> response = request.parseNetworkResponse(
            new NetworkResponse(entry.data, entry.responseHeaders));
    request.addMarker("cache-hit-parsed");

如果緩存未命中,通過NetworkDispatcher進(jìn)行網(wǎng)絡(luò)請(qǐng)求,則會(huì)調(diào)用如下方法對(duì)結(jié)果進(jìn)行解析:

    // Parse the response here on the worker thread.
    Response<?> response = request.parseNetworkResponse(networkResponse);
    request.addMarker("network-parse-complete");

不管是緩存命中(CacheDispatcher)還是不命中(NetworkDispatcher),都會(huì)通過request.parseNetworkResponse(response)的方式將網(wǎng)絡(luò)結(jié)果的解析回調(diào)到Request中每强。則對(duì)于Request的子類實(shí)現(xiàn)ImageRequest,其解析方法實(shí)現(xiàn)如下代碼所示:

    @Override
    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
        // Serialize all decode on a global lock to reduce concurrent heap usage.
        synchronized (sDecodeLock) {
            try {
                return doParse(response);
            } catch (OutOfMemoryError e) {
                VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
                return Response.error(new ParseError(e));
            }
        }
    }

其中doParse()會(huì)進(jìn)行對(duì)reponse的字節(jié)留進(jìn)行處理,按請(qǐng)求參數(shù)包裝成相應(yīng)的Bitmap,其關(guān)鍵代碼如下所示始腾。

  byte[] data = response.data;
  BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
  Bitmap bitmap = null;
  if (mMaxWidth == 0 && mMaxHeight == 0) {
      decodeOptions.inPreferredConfig = mDecodeConfig;
      bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
  } else {
      // If we have to resize this image, first get the natural bounds.
      decodeOptions.inJustDecodeBounds = true;
      BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
      int actualWidth = decodeOptions.outWidth;
      int actualHeight = decodeOptions.outHeight;
      // Then compute the dimensions we would ideally like to decode to.
      int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
              actualWidth, actualHeight, mScaleType);
      int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
              actualHeight, actualWidth, mScaleType);
      // Decode to the nearest power of two scaling factor.
      decodeOptions.inJustDecodeBounds = false;
      // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
      // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
      decodeOptions.inSampleSize =
          findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
      Bitmap tempBitmap =
          BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

      // If necessary, scale down to the maximal acceptable size.
      if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
              tempBitmap.getHeight() > desiredHeight)) {
          bitmap = Bitmap.createScaledBitmap(tempBitmap,
                  desiredWidth, desiredHeight, true);
          tempBitmap.recycle();
      } else {
          bitmap = tempBitmap;
      }
  }
  if (bitmap == null) {
      return Response.error(new ParseError(response));
  } else {
      return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
  }

注意這段解析代碼中maxWidth與maxHeight是用戶初始化傳入的請(qǐng)求參數(shù),如果調(diào)用方不傳的話,默認(rèn)為0,不進(jìn)行縮放。則解析的bitmap是原圖的尺寸大小,而如果調(diào)用方傳入?yún)?shù),則需要對(duì)圖片進(jìn)行縮放(PS:這段縮放代碼其實(shí)也是很有參考意義的)空执。在縮放完成后,將請(qǐng)求結(jié)果轉(zhuǎn)入到Response對(duì)象中返回浪箭。之后,通過ResponseDelivery調(diào)用postResponse(reqeust, response)將結(jié)果回調(diào)到Listener的時(shí)候,通過deliverResponse回調(diào)的就是具體的解析結(jié)果了(從這里我們也可以看出,如果有自定義Request需要的時(shí)候,只需要繼承Request,重寫parseNetworkResponse與deliverResponse方法即可實(shí)現(xiàn)Volley中Request的自定義結(jié)果解析與回調(diào))。

Volley ImageLoader 請(qǐng)求分析

ImageLoader中只有一個(gè)構(gòu)造函數(shù),它需要傳入RequestQueue(我們從這點(diǎn)可以猜想ImageLoader可能也會(huì)依賴ImageReqeust進(jìn)行請(qǐng)求)和ImageCache對(duì)象,而ImageCache是定義于ImageLoader類中的接口辨绊。
對(duì)于ImageCache說明如下源碼所示:

  /**
     Simple cache adapter interface. If provided to the ImageLoader, it
     will be used as an L1 cache before dispatch to Volley. Implementations
     must not block. Implementation with an LruCache is recommended.
  */
  public interface ImageCache {
      public Bitmap getBitmap(String url);
      public void putBitmap(String url, Bitmap bitmap);
  }

這段代碼的說明中注釋說的很清楚奶栖,大意為ImageCache是一個(gè)簡(jiǎn)單的緩存適配接口,它提供給Volley作為一級(jí)緩存,其實(shí)現(xiàn)不能產(chǎn)生阻塞。推薦使用LruCache來實(shí)現(xiàn)這個(gè)接口门坷。在上面的示例中,使用ImageCache直接返回了NULL,所以并沒有起到緩存的效果宣鄙。

ImageListener作為Imageloader中的一個(gè)接口,其繼承了Response中的ErrorListener,從這個(gè)角度也可以看的出,Imageloader底層是通過Request執(zhí)行的請(qǐng)求。而對(duì)于ImageListener的獲取,如下代碼所示:

    public static ImageListener getImageListener(final ImageView view,
            final int defaultImageResId, final int errorImageResId) {
        return new ImageListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                if (errorImageResId != 0) {
                    view.setImageResource(errorImageResId);
                }
            }

            @Override
            public void onResponse(ImageContainer response, boolean isImmediate) {
                if (response.getBitmap() != null) {
                    view.setImageBitmap(response.getBitmap());
                } else if (defaultImageResId != 0) {
                    view.setImageResource(defaultImageResId);
                }
            }
        };
    }

我們從默認(rèn)實(shí)現(xiàn)也可以看的出,在response回調(diào)中會(huì)去Response中獲取Bitmap,所以我們可以做一個(gè)推論:ImageLoader是通過ImageReqeust請(qǐng)求并對(duì)請(qǐng)求結(jié)果進(jìn)行封裝的一種圖片加載方式默蚌。接下來我們對(duì)ImageLoader的加載過程進(jìn)行詳細(xì)分析冻晤。在為ImageLoader綁定注冊(cè)監(jiān)聽回調(diào)后,我們可以使用ImageLoader的get方式加載圖片。其中g(shù)et有三種多態(tài)方法,如下所示:

  public ImageContainer get(String requestUrl, final ImageListener listener) {
      return get(requestUrl, listener, 0, 0);
  }

  public ImageContainer get(String requestUrl, ImageListener imageListener,
          int maxWidth, int maxHeight) {
      return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
  }

  public ImageContainer get(String requestUrl, ImageListener imageListener,
          int maxWidth, int maxHeight, ScaleType scaleType) {
      ...
  }

我們可以看到,其中最終都是通過第三種方式進(jìn)行獲取,其中依賴傳入?yún)?shù)數(shù):url,listener,maxWidth,maxHeight,scaleType,如果不傳ScaleType,默認(rèn)的ScaleType為CenterInside绸吸。在ImageLoader通過get方式提交請(qǐng)求后鼻弧,會(huì)首先到設(shè)置的一級(jí)緩存中查詢圖片緩存设江,如果緩存沒有命中,則將request加入到RequestQueue中開始一個(gè)新請(qǐng)求攘轩,同時(shí)在mInFlightRequests中將請(qǐng)求加到請(qǐng)求隊(duì)列中叉存。源碼如下:

    public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight, ScaleType scaleType) {

        // only fulfill requests that were initiated from the main thread.
        throwIfNotOnMainThread();

        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

        // Try to look up the request in the cache of remote images.
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // Return the cached bitmap.
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }

        // The bitmap did not exist in the cache, fetch it!
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // Update the caller to let them know that they should use the default bitmap.
        imageListener.onResponse(imageContainer, true);

        // Check to see if a request is already in-flight.
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // If it is, add this request to the list of listeners.
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
                cacheKey);

        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }

PS:這段代碼會(huì)檢查是否運(yùn)行在主線程,如果運(yùn)行在子線程中度帮,則會(huì)直接拋出異常的歼捏。
其中,ImageContainer是ImageLoader的內(nèi)部類,在源碼中的注釋對(duì)它的介紹是:Container object for all of the data surrounding an image request.,意思是一個(gè)ImageRequest的數(shù)據(jù)對(duì)象包裝類笨篷,它作為get方法的返回值瞳秽,當(dāng)有緩存的時(shí)候,它會(huì)將數(shù)據(jù)取出冕屯,直接回調(diào)同時(shí)返回包裝類寂诱。而若不存在緩存拂苹,則先創(chuàng)建ImageContainer,回調(diào)listenert通知顯示設(shè)置的默認(rèn)圖片安聘。

接著這里會(huì)出現(xiàn)一個(gè)新的類型BatchedImageRequest,還有一個(gè)類型為HashMap的mInFlightRequests瓢棒,mInFlightRequests是用來保存當(dāng)前已存在的Request,若有相同請(qǐng)求時(shí)浴韭,它會(huì)將ImageContainer加入到BatchedImageRquest中,而BatchedImageRequest是ImageLoader的內(nèi)部類脯宿,它是Request的包裝類念颈,其中有一個(gè)LinkedList去存儲(chǔ)ImageContainer。

當(dāng)有新的請(qǐng)求连霉,并將請(qǐng)求請(qǐng)求加入到請(qǐng)求隊(duì)列中的時(shí)候榴芳,就進(jìn)入了Volley的網(wǎng)絡(luò)請(qǐng)求邏輯中,這個(gè)我們前文已經(jīng)分析過跺撼,這里就不再贅述窟感。

在請(qǐng)求響應(yīng)后(以響應(yīng)成功為例),則會(huì)遍歷所有的ImageContainer歉井,回調(diào)到batchedRequest耦合的ImageContainer注冊(cè)的ImageListener中柿祈。部分代碼如下:

    protected void onGetImageSuccess(String cacheKey, Bitmap response) {
        // cache the image that was fetched.
        mCache.putBitmap(cacheKey, response);

        // remove the request from the list of in-flight requests.
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

        if (request != null) {
            // Update the response bitmap.
            request.mResponseBitmap = response;

            // Send the batched response
            batchResponse(cacheKey, request);
        }
    }
    private void batchResponse(String cacheKey, BatchedImageRequest request) {
        mBatchedResponses.put(cacheKey, request);
        // If we don't already have a batch delivery runnable in flight, make a new one.
        // Note that this will be used to deliver responses to all callers in mBatchedResponses.
        if (mRunnable == null) {
            mRunnable = new Runnable() {
                @Override
                public void run() {
                    for (BatchedImageRequest bir : mBatchedResponses.values()) {
                        for (ImageContainer container : bir.mContainers) {
                            // If one of the callers in the batched request canceled the request
                            // after the response was received but before it was delivered,
                            // skip them.
                            if (container.mListener == null) {
                                continue;
                            }
                            if (bir.getError() == null) {
                                container.mBitmap = bir.mResponseBitmap;
                                container.mListener.onResponse(container, false);
                            } else {
                                container.mListener.onErrorResponse(bir.getError());
                            }
                        }
                    }
                    mBatchedResponses.clear();
                    mRunnable = null;
                }

            };
            // Post the runnable.
            mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
        }
    }

在加載完成后,將緩存中的Request清空哩至,這樣整個(gè)請(qǐng)求就完成了躏嚎。但這里還有一個(gè)問題,ImageLoader中默認(rèn)構(gòu)建的ImageListener的onResponse回調(diào)方法如下所示:

      public void onResponse(ImageContainer response, boolean isImmediate) {
            if (response.getBitmap() != null) {
                view.setImageBitmap(response.getBitmap());
            } else if (defaultImageResId != 0) {
                view.setImageResource(defaultImageResId);
            }
        }

其中的isImmediate參數(shù)并未使用到菩貌,這個(gè)參數(shù)從其注釋定義有這樣的表述:isImmediate True if this was called during ImageLoader.get() variants.This can be used to differentiate between a cached image loading and a network image loading in order to, for example, run an animation to fade in network loaded images.大意是指:這個(gè)參數(shù)如果是true,表明listener被調(diào)用是通過ImageLoader.get(),可以用來被區(qū)別圖片是來自緩存還是網(wǎng)絡(luò)加載卢佣。比如說在加載網(wǎng)絡(luò)圖片時(shí)來做一個(gè)淡入動(dòng)畫。但是我目前沒看出這個(gè)參數(shù)有什么具體的實(shí)際意義箭阶,如果大家有什么想法可以在留言中提出虚茶。

Volley NetworkImageView 圖片加載分析

對(duì)于第三種使用NetworkIMageView請(qǐng)求方式進(jìn)行的圖片加載,我們可以看到,在配置文件中配置后,卻是使用ImageLoader進(jìn)行加載的.在初始化NetworkImage后,設(shè)置好url就開始加載邏輯了晚缩。

    public void setImageUrl(String url, ImageLoader imageLoader) {
        mUrl = url;
        mImageLoader = imageLoader;
        // The URL has potentially changed. See if we need to load it.
        loadImageIfNecessary(false);
    }

調(diào)用loadImageIfNecessary()方法進(jìn)行圖片加載,而這個(gè)方法在NetworkImage中的onlayout中同樣會(huì)調(diào)用代碼如下所示(我沒想明白有什么場(chǎng)景是需要在在onLayout的時(shí)候去加載圖片的╮(╯▽╰)╭):

  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        loadImageIfNecessary(true);
  }

下面分析loadImageIfNecessary方法,我們可以看到如果傳入url為空,則調(diào)用setDefaultImageOrNull()方法設(shè)置默認(rèn)圖片,否則的話才會(huì)繼續(xù)加載圖片。

 // if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if (TextUtils.isEmpty(mUrl)) {
    if (mImageContainer != null) {
        mImageContainer.cancelRequest();
        mImageContainer = null;
    }
    setDefaultImageOrNull();
    return;
}

假如有相同的圖片在加載媳危,則會(huì)直接返回荞彼。否則的話,會(huì)將之前的那個(gè)Request取消待笑,重新加載現(xiàn)有請(qǐng)求鸣皂。

// if there was an old request in this view, check if it needs to be canceled.
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
    if (mImageContainer.getRequestUrl().equals(mUrl)) {
        // if the request is from the same URL, return.
        return;
    } else {
        // if there is a pre-existing request, cancel it if it's fetching a different URL.
        mImageContainer.cancelRequest();
        setDefaultImageOrNull();
    }
}

最后,這些條件都不滿足暮蹂,則通過mImageLoader.get()使用ImageLoader進(jìn)行請(qǐng)求加載寞缝,這樣就進(jìn)入了ImageLoader的加載邏輯。這里仰泻,我們也找到了上述疑惑的地方荆陆,在NetworkImageView中創(chuàng)建的ImageListener 回調(diào)中有如下邏輯:

public void onResponse(final ImageContainer response, boolean isImmediate) {
    // If this was an immediate response that was delivered inside of a layout
   // pass do not set the image immediately as it will trigger a requestLayout
   // inside of a layout. Instead, defer setting the image by posting back to
   // the main thread.
  if (isImmediate && isInLayoutPass) {
      post(new Runnable() {
          @Override
          public void run() {
               onResponse(response, false);
          }
       });
       return;
  }
   if (response.getBitmap() != null) {
            setImageBitmap(response.getBitmap());
   } else if (mDefaultImageId != 0) {
            setImageResource(mDefaultImageId);
    }
}

對(duì)于這段中的邏輯理解不了╮(╯▽╰)╭,有朋友有思路可以留言解惑集侯。

結(jié)語

我們可以看出,ImageLoader對(duì)于圖片邏輯的處理主要依賴于Request與RequestQueue框架,雖然整體使用較為繁瑣被啼,但是Volley對(duì)相關(guān)設(shè)置預(yù)留了擴(kuò)展,總體來說如果使用Volley做網(wǎng)絡(luò)庫棠枉,但是又不想引入其他圖片框架加大包體積的話浓体,使用Volley來做圖片加載也是一種不錯(cuò)的選擇。

對(duì)于網(wǎng)絡(luò)請(qǐng)求緩存的源碼分析已經(jīng)更新辈讶,詳情點(diǎn)擊:

PS:整體層次圖命浴,僅供參考,如果不足贱除,歡迎指出O(∩_∩)O哈生闲!

圖片請(qǐng)求整體框架層次圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市月幌,隨后出現(xiàn)的幾起案子碍讯,更是在濱河造成了極大的恐慌,老刑警劉巖飞醉,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冲茸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡缅帘,警方通過查閱死者的電腦和手機(jī)轴术,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钦无,“玉大人逗栽,你說我怎么就攤上這事∈г荩” “怎么了彼宠?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵鳄虱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我凭峡,道長(zhǎng)拙已,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任摧冀,我火速辦了婚禮倍踪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘索昂。我一直安慰自己建车,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布椒惨。 她就那樣靜靜地躺著缤至,像睡著了一般。 火紅的嫁衣襯著肌膚如雪康谆。 梳的紋絲不亂的頭發(fā)上领斥,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音秉宿,去河邊找鬼戒突。 笑死,一個(gè)胖子當(dāng)著我的面吹牛描睦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播导而,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼忱叭,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了今艺?” 一聲冷哼從身側(cè)響起韵丑,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虚缎,沒想到半個(gè)月后撵彻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡实牡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年陌僵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片创坞。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碗短,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出题涨,到底是詐尸還是另有隱情偎谁,我是刑警寧澤总滩,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站巡雨,受9級(jí)特大地震影響闰渔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铐望,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一澜建、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝌以,春花似錦炕舵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至徊件,卻和暖如春奸攻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虱痕。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來泰國打工睹耐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人部翘。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓硝训,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親新思。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窖梁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

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