Picasso源碼解析及優(yōu)化見解

Picasso是一款圖片加載庫出自Square葛闷,以小巧功能齊全出名,本文將從源碼解析Picasso的整個加載流程双藕。


Picasso時序圖

時序圖為筆者根據(jù)整個調(diào)用流程所畫淑趾,有誤私聊筆者進行修改

整個流程

  • 調(diào)用Picasso創(chuàng)建一個RequestCreator,并返回
  public RequestCreator load(@Nullable String path) {
    if (path == null) {
      return new RequestCreator(this, null, 0);
    }
    if (path.trim().length() == 0) {
      throw new IllegalArgumentException("Path must not be empty.");
    }
    return load(Uri.parse(path));
  }

  public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
  }
  • RequestCreator設置請求參數(shù)忧陪,url扣泊、資源id驳概、transform、fix等
  • 調(diào)用RequestCreator的into旷赖,設置了fix的情況下顺又,如果控件已經(jīng)獲取到尺寸就創(chuàng)建Action,否則就創(chuàng)建一DeferredRequestCreator(延遲請求)等孵,獲取到后重新into
class DeferredRequestCreator implements OnPreDrawListener, OnAttachStateChangeListener {
  private final RequestCreator creator;
  @VisibleForTesting final WeakReference<ImageView> target;
  @VisibleForTesting Callback callback;

  DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {
    this.creator = creator;
    this.target = new WeakReference<>(target);
    this.callback = callback;

    target.addOnAttachStateChangeListener(this);

    // Only add the pre-draw listener if the view is already attached.
    // See: https://github.com/square/picasso/issues/1321
    if (target.getWindowToken() != null) {
      onViewAttachedToWindow(target);
    }
  }

  @Override public void onViewAttachedToWindow(View view) {
    view.getViewTreeObserver().addOnPreDrawListener(this);
  }

  @Override public void onViewDetachedFromWindow(View view) {
    view.getViewTreeObserver().removeOnPreDrawListener(this);
  }

  @Override public boolean onPreDraw() {
    ImageView target = this.target.get();
    if (target == null) {
      return true;
    }

    ViewTreeObserver vto = target.getViewTreeObserver();
    if (!vto.isAlive()) {
      return true;
    }

    int width = target.getWidth();
    int height = target.getHeight();

    if (width <= 0 || height <= 0) {
      return true;
    }

    target.removeOnAttachStateChangeListener(this);
    vto.removeOnPreDrawListener(this);
    this.target.clear();
    //控件已經(jīng)獲取到寬高稚照,重新into
    this.creator.unfit().resize(width, height).into(target, callback);
    return true;
  }

  void cancel() {
  ...省略
  }
}

  • 調(diào)用Picasso.enqueueAndSubmit(Action)->Dispatcher.performSubmit(Action)->ExecutorService.submit(BitmapHunter)將請求提交給線程池去處理
  void performSubmit(Action action, boolean dismissFailed) {
    //已暫停的,就結(jié)束了
    if (pausedTags.contains(action.getTag())) {
      pausedActions.put(action.getTarget(), action);
      if (action.getPicasso().loggingEnabled) {
        Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_PAUSED, action.request.logId(),
            "because tag '" + action.getTag() + "' is paused");
      }
      return;
    }
    //通過請求key獲取BitmapHunter核心類俯萌,這一步的主要作用是果录,一個圖片有兩個控價都需要使用她,
    //避免重復請求咐熙。這里的attach方法就是將action弱恒,添加當前這個BitmapHunter的action列表中
    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }

    if (service.isShutdown()) {
      if (action.getPicasso().loggingEnabled) {
        Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_IGNORED, action.request.logId(), "because shut down");
      }
      return;
    }
    //策略模式,選取可以請求該種類型
   //根據(jù)傳入的action中的Request資源地址來判定使用那種請求處理器
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
   //提交線程池棋恼,請求圖片
    hunter.future = service.submit(hunter);
    //保存任務句柄(取消時可用)
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_ENQUEUED, action.request.logId());
    }
  }
  • BitmapHunter執(zhí)行run方法獲取圖片資源返弹,run方法調(diào)用hunt獲取bitmap,并執(zhí)行Transformation
 @Override public void run() {
    try {
      result = hunt();

      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
    } 
    ...省略
  }

  Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    //讀取策略爪飘,是否可以讀取緩存
    if (MemoryPolicy.shouldReadFromMemoryCache(memoryPolicy)) {
      //根據(jù)請求key獲取緩存bitmap
      bitmap = cache.get(key);
      if (bitmap != null) {
        //記錄緩存熱度
        stats.dispatchCacheHit();
        loadedFrom = Picasso.LoadedFrom.MEMORY;
        if (picasso.loggingEnabled) {
          Utils.log(Utils.OWNER_HUNTER, Utils.VERB_DECODED, data.logId(), "from cache");
        }
        return bitmap;
      }
    }
    //沒有緩存或者不允許讀取緩存义起,繼續(xù)往下執(zhí)行
    
    networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    //請求處理器load圖片資源
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifOrientation = result.getExifOrientation();
      bitmap = result.getBitmap();

      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        Source source = result.getSource();
        try {
          //將流文件轉(zhuǎn)為bitmap
          bitmap = decodeStream(source, data);
        } finally {
          try {
            //noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
            source.close();
          } catch (IOException ignored) {
          }
        }
      }
    }

    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        Utils.log(Utils.OWNER_HUNTER, Utils.VERB_DECODED, data.logId());
      }
      //狀態(tài)更新
      stats.dispatchBitmapDecoded(bitmap);
      //是否需要transformation,必須調(diào)用RequestCreator的fix()方法师崎,默認的Transformation才會被調(diào)用默终。會根據(jù)獲取到控件的size調(diào)整圖
      if (data.needsTransformation() || exifOrientation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifOrientation != 0) {
            //默認的transformation,會根據(jù)控件大小調(diào)整bitmap
            bitmap = transformResult(data, bitmap, exifOrientation);
            if (picasso.loggingEnabled) {
              Utils.log(Utils.OWNER_HUNTER, Utils.VERB_TRANSFORMED, data.logId());
            }
          }
          if (data.hasCustomTransformations()) {
             //自定義的transformation,圓角圖片啥的變換
            bitmap = applyCustomTransformations(data.transformations, bitmap);
            if (picasso.loggingEnabled) {
              Utils.log(Utils.OWNER_HUNTER, Utils.VERB_TRANSFORMED, data.logId(), "from custom transformations");
            }
          }
        }
        if (bitmap != null) {
          stats.dispatchBitmapTransformed(bitmap);
        }
      }
    }

    return bitmap;
  }

從上面的源碼可知緩存邏輯:內(nèi)存-->本地緩存(由okhttp提供)-->網(wǎng)絡犁罩,Picasso一共提供了7圖片請求處理器齐蔽,這里著重介紹哈網(wǎng)絡處理器
NetworkRequestHandler.java

class NetworkRequestHandler extends RequestHandler {
  private static final String SCHEME_HTTP = "http";
  private static final String SCHEME_HTTPS = "https";

  private final Downloader downloader;
  private final Stats stats;

  NetworkRequestHandler(Downloader downloader, Stats stats) {
    this.downloader = downloader;
    this.stats = stats;
  }
  //這個處理器,可以處理那種類型的資源床估,scheme是http和https的
  @Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }
  //加載并返回result
  @Override public Result load(Request request, int networkPolicy) throws IOException {
    //創(chuàng)建okhttp request
    Request downloaderRequest = createRequest(request, networkPolicy);
    //OkHttp3Downloader的load方法去下載資源
    Response response = downloader.load(downloaderRequest);
    ResponseBody body = response.body();

    if (!response.isSuccessful()) {
      body.close();
      throw new ResponseException(response.code(), request.networkPolicy);
    }
    //資源來源含滴,網(wǎng)絡/磁盤
    Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
    if (loadedFrom == DISK && body.contentLength() == 0) {
      body.close();
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && body.contentLength() > 0) {
      stats.dispatchDownloadFinished(body.contentLength());
    }
    //返回結(jié)果
    return new Result(body.source(), loadedFrom);
  }
...省略
}

OkHttp3Downloader下載器源碼

public final class OkHttp3Downloader implements Downloader {
  @VisibleForTesting final Call.Factory client;
  private final Cache cache;
  private boolean sharedClient = true;

  //初始化獲設置緩存目錄默認:/data/data/包名/cache/picasso-cache下
  public OkHttp3Downloader(final Context context) {
    this(Utils.createDefaultCacheDir(context));
  }

  //初始化獲設置緩存
  public OkHttp3Downloader(final File cacheDir) {
    this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
  }

  //初始化獲設置緩存
  public OkHttp3Downloader(final Context context, final long maxSize) {
    this(Utils.createDefaultCacheDir(context), maxSize);
  }

  /**
   * Create new downloader that uses OkHttp. This will install an image cache into the specified
   * directory.
   *
   * @param cacheDir The directory in which the cache should be stored
   * @param maxSize The size limit for the cache.
   */
  public OkHttp3Downloader(final File cacheDir, final long maxSize) {
    this(new OkHttpClient.Builder().cache(new Cache(cacheDir, maxSize)).build());
    sharedClient = false;
  }
  //load執(zhí)行同步方法去加載網(wǎng)絡上的圖片,是否使用本地緩存的圖片顷窒,這里有okhttp去實現(xiàn)
  //如果需要更換網(wǎng)絡訪問框架蛙吏,需要實現(xiàn)1.網(wǎng)絡下載圖片源哩; 2. 緩存圖片到本地鞋吉,下次請求時,返回緩存圖片
  @NonNull @Override public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
  }
  ...省略
}
  • Dispatcher.dispatchComplete最終調(diào)用了performComplete方法
  void performComplete(BitmapHunter hunter) {
    //是否緩存到內(nèi)次
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    //移除進行時的bitmap請求
    hunterMap.remove(hunter.getKey());
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_BATCHED, Utils.getLogIdsForHunter(hunter), "for completion");
    }
  }

  private void batch(BitmapHunter hunter) {
    if (hunter.isCancelled()) {
      return;
    }
    if (hunter.result != null) {
     //bitmap的預畫
      hunter.result.prepareToDraw();
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
  }

最終調(diào)用到了調(diào)用Picasso類的deliverAction方法

  private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
    if (action.isCancelled()) {//取消
      return;
    }
    if (!action.willReplay()) {//將重試
      targetToAction.remove(action.getTarget());
    }
    if (result != null) {
      if (from == null) {
        throw new AssertionError("LoadedFrom cannot be null.");
      }
     //調(diào)用action的complete
      action.complete(result, from);
      if (loggingEnabled) {
        Utils.log(Utils.OWNER_MAIN, Utils.VERB_COMPLETED, action.request.logId(), "from " + from);
      }
    } else {
      action.error(e);
      if (loggingEnabled) {
        Utils.log(Utils.OWNER_MAIN, Utils.VERB_ERRORED, action.request.logId(), e.getMessage());
      }
    }
  }

Picasso提供了4種Actiong励烦,這里貼哈ImageViewAction

class ImageViewAction extends Action<ImageView> {

  Callback callback;

  ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
      int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
      Callback callback, boolean noFade) {
    super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
        tag, noFade);
    this.callback = callback;
  }
  //加載完成時
  @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    if (result == null) {
      throw new AssertionError(
          String.format("Attempted to complete action with no result!\n%s", this));
    }

    ImageView target = this.target.get();
    if (target == null) {
      return;
    }

    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    //設置bitmap到target谓着,也就是我們的目標view
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
    //回調(diào)
    if (callback != null) {
      callback.onSuccess();
    }
  }
  
  //加載失敗時
  @Override public void error(Exception e) {
    ImageView target = this.target.get();
    if (target == null) {
      return;
    }
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof Animatable) {
      ((Animatable) placeholder).stop();
    }
    if (errorResId != 0) {
      target.setImageResource(errorResId);
    } else if (errorDrawable != null) {
      target.setImageDrawable(errorDrawable);
    }

    if (callback != null) {
      callback.onError(e);
    }
  }
  
//取消
  @Override void cancel() {
    super.cancel();
    if (callback != null) {
      callback = null;
    }
  }
}

Picasso加載流程就已經(jīng)解析完了,網(wǎng)絡和本地緩存都依賴OKhttp坛掠,我們整個項目圖片加載不多赊锚,項目中使用的OKhttp治筒,所以我選用了Picasso來減少代碼量。

Picasso需要優(yōu)化點

  1. 內(nèi)存緩存舷蒲,可以看到是通過requestKey+Bitmap存入LruCache耸袜,但是requestkey上帶有尺寸旋轉(zhuǎn)角度等參數(shù),也就是說同一張圖片牲平,因為尺寸等參數(shù)不同就會造成儲存了多張bitmap堤框,bitmap(吃內(nèi)存大戶啊)
    Utils下的requestKey源碼
static String createKey(Request data, StringBuilder builder) {
    if (data.stableKey != null) {
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }
    builder.append(KEY_SEPARATOR);

    if (data.rotationDegrees != 0) {
      builder.append("rotation:").append(data.rotationDegrees);
      if (data.hasRotationPivot) {
        builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
      }
      builder.append(KEY_SEPARATOR);
    }
    if (data.hasSize()) {
      builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
      builder.append(KEY_SEPARATOR);
    }
    if (data.centerCrop) {
      builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
    } else if (data.centerInside) {
      builder.append("centerInside").append(KEY_SEPARATOR);
    }

    if (data.transformations != null) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, count = data.transformations.size(); i < count; i++) {
        builder.append(data.transformations.get(i).key());
        builder.append(KEY_SEPARATOR);
      }
    }

    return builder.toString();
  }

解決方案:
按照地址生成key纵柿,保存bitmap最大的一張蜈抓,取出bitmap后發(fā)現(xiàn)比當前大,就Transformation昂儒。比請求的小沟使,就再去請求一次原圖(這里有磁盤緩存),Transformation后保存原圖

  1. 及時調(diào)用Picasso.shutdown()渊跋,此方法會停止Picasso和清理緩存腊嗡,只有在確定不需要使用Picasso時調(diào)用

Picasso如何防止傳入的View內(nèi)存泄露

  1. 弱引用View
  2. 引用隊列保留action,發(fā)現(xiàn)View被gc后取消請求
  • Picasso實例化時啟動清理線程拾酝,初始化應用對象
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...省略
   //創(chuàng)建引用隊列
    this.referenceQueue = new ReferenceQueue<>();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    //啟動清理線程
    this.cleanupThread.start();
  }
  • Action實例化時用WeakReference包裹View
Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,
      int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
    //使用WeakReference持有View叽唱,并傳入引用隊列,當View被GC時當前RequestWeakReference對象會被放入referenceQueue中
    this.target =
        target == null ? null : new RequestWeakReference<>(this, target, picasso.referenceQueue);
...省略
  }
  • RequestWeakReference中有一個請求的Action
  static class RequestWeakReference<M> extends WeakReference<M> {
    //請求action
    final Action action;

    RequestWeakReference(Action action, M referent, ReferenceQueue<? super M> q) {
      super(referent, q);
      this.action = action;
    }
  }

-清理線程微宝,不停的從引用隊列中取出RequestWeakReference對象棺亭,回收RequestWeakReference對象

  private static class CleanupThread extends Thread {
    private final ReferenceQueue<Object> referenceQueue;
    private final Handler handler;

    CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {
      //應用隊列
      this.referenceQueue = referenceQueue;
      this.handler = handler;
      setDaemon(true);
      setName(Utils.THREAD_PREFIX + "refQueue");
    }

    @Override public void run() {
      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
      while (true) {
        try {
          //回收RequestWeakReference對象,并發(fā)送消息給Handler取消Action
          //remove超時時間1s蟋软,如果1s任然為null镶摘,則直接返回null
          Action.RequestWeakReference<?> remove =
              (Action.RequestWeakReference<?>) referenceQueue.remove(Utils.THREAD_LEAK_CLEANING_MS);
          Message message = handler.obtainMessage();
          if (remove != null) {
            message.what = REQUEST_GCED;
            message.obj = remove.action;
            handler.sendMessage(message);
          } else {
            message.recycle();
          }
        } catch (InterruptedException e) {
          break;
        } catch (final Exception e) {
          handler.post(new Runnable() {
            @Override public void run() {
              throw new RuntimeException(e);
            }
          });
          break;
        }
      }
    }

    void shutdown() {
      interrupt();
    }
  }
  • Action取消請求
  case REQUEST_GCED: {
          Action action = (Action) msg.obj;
          if (action.getPicasso().loggingEnabled) {
            Utils.log(Utils.OWNER_MAIN, Utils.VERB_CANCELED, action.request.logId(), "target got garbage collected");
          }
          action.picasso.cancelExistingRequest(action.getTarget());
          break;
        }

整個清理流程:

  1. View被RequestWeakReference對象持有, RequestWeakReference被Action持有岳守,Action也被RequestWeakReference持有
  2. RequestWeakReference內(nèi)的持有對象被回收后凄敢,加入到引用隊列ReferenceQueue
  3. CleanupThread在Picasso創(chuàng)建時被啟動,不停的從ReferenceQueue對象中移除RequestWeakReference對象
  4. 在移除對象時得到RequestWeakReference中action對象湿痢,發(fā)送消息取消Action
  5. Action和RequestWeakReference是相互引用關系涝缝,其它地方都已經(jīng)釋放,所以都可以被gc了
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末譬重,一起剝皮案震驚了整個濱河市拒逮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臀规,老刑警劉巖滩援,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異塔嬉,居然都是意外死亡玩徊,警方通過查閱死者的電腦和手機租悄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恩袱,“玉大人泣棋,你說我怎么就攤上這事∨纤” “怎么了外傅?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俩檬。 經(jīng)常有香客問我萎胰,道長,這世上最難降的妖魔是什么棚辽? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任技竟,我火速辦了婚禮,結(jié)果婚禮上屈藐,老公的妹妹穿的比我還像新娘榔组。我一直安慰自己,他們只是感情好联逻,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布搓扯。 她就那樣靜靜地躺著,像睡著了一般包归。 火紅的嫁衣襯著肌膚如雪锨推。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天公壤,我揣著相機與錄音换可,去河邊找鬼。 笑死厦幅,一個胖子當著我的面吹牛沾鳄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播确憨,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼译荞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了休弃?” 一聲冷哼從身側(cè)響起吞歼,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玫芦,沒想到半個月后浆熔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡桥帆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年医增,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片老虫。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡叶骨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祈匙,到底是詐尸還是另有隱情忽刽,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布夺欲,位于F島的核電站跪帝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏些阅。R本人自食惡果不足惜伞剑,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望市埋。 院中可真熱鬧黎泣,春花似錦、人聲如沸缤谎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坷澡。三九已至托呕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間频敛,已是汗流浹背镣陕。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姻政,地道東北人呆抑。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像鹊碍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耀销,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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