加載庫源碼淺析之 Picasso

Picasso 作為 Android 上一個(gè)老牌的圖片加載庫,似乎近些年在 Glide 的 “打壓” 下已經(jīng)變的黯然失色旗闽,但作為 square 出品的優(yōu)秀框架神汹,其實(shí)現(xiàn)的架構(gòu)和思想仍然有許多值得借鑒和學(xué)習(xí)的地方曹仗,本文所使用的 Picasso 版本號為 2.71828,在 gradle 中依賴如下:

implementation 'com.squareup.picasso:picasso:2.71828'

Picasso 加載圖片的方式和 Glide 類似军掂,都是鏈?zhǔn)秸{(diào)用:

Picasso.get().load("").into(target); // 異步加載并設(shè)置到 target 上
Picasso.get().load("").get(); //同步加載轮蜕,不能在主線程調(diào)用
Picasso.get().load("").fetch(); // 異步加載

這里 Picasso 提供了三種方式:

  • into() :參數(shù)可以是 View,如 ImageView 和 RemoteViews蝗锥,也可以是一個(gè) Target 類型的對象跃洛,等于是通過回調(diào)的方式來實(shí)現(xiàn)加載結(jié)果的處理
  • get():同步獲取 btimap 對象,會檢查當(dāng)前線程是否是主線程终议,如果是則直接會拋出異常
  • fetch():異步獲取 bitmap 對象汇竭,可以通過設(shè)置回調(diào)來監(jiān)聽是否加載成功

使用 into 加載的過程大致上可以分為三個(gè)階段:一是獲取 Picasso 對象,對應(yīng) Picasso.get()穴张,這一步是初始化如線程池细燎、緩存等;二是請求封裝皂甘,對應(yīng) load(url)玻驻,這一步則是對資源請求的封裝,例如占位圖偿枕、加載失敗圖璧瞬、tag、資源裁剪等益老;最后則是請求處理階段彪蓬,對應(yīng) into(ImageView) ,這一步處理的事情最為繁重捺萌,包括資源請求、資源處理等膘茎,下圖為 into(ImageView) 流程的的序列圖桃纯,接下來就通過圍繞這個(gè)序列圖來進(jìn)行分析:

序列圖.png

獲取 Picasso 對象

這一節(jié)對應(yīng)于序列圖中的步驟 1、2披坏、3态坦。

靜態(tài)方法 get() 是通過 DCL 實(shí)現(xiàn)的單例模式,返回一個(gè)全局的 Picass 對象:

public static Picasso get() {
  if (singleton == null) {
    synchronized (Picasso.class) {
      if (singleton == null) {
        if (PicassoProvider.context == null) {
          throw new IllegalStateException("context == null");
        }
        singleton = new Builder(PicassoProvider.context).build();
      }
    }
  }
  return singleton;
}

這里 context 的獲取是通過 PicassoProvider 實(shí)現(xiàn)的棒拂, PicassoProvider 繼承自 ContentProvider伞梯,主要作用就是為 Picasso 提供 context 對象:

public final class PicassoProvider extends ContentProvider {

  @SuppressLint("StaticFieldLeak") static Context context;

  @Override public boolean onCreate() {
    context = getContext();
    return true;
  }
  ...
}

并在自己的 AndroidManifest 文件中注冊:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.picasso" >

    <uses-sdk android:minSdkVersion="14" />

    <application>
        <provider
            android:name="com.squareup.picasso.PicassoProvider"
            android:authorities="${applicationId}.com.squareup.picasso"
            android:exported="false" />
    </application>

</manifest>

由于在進(jìn)程啟動(dòng)時(shí) ContentProvider 的加載是優(yōu)先于 Application 的加載玫氢,因此很多三方 SDK 也通常使用這種方式來自動(dòng)進(jìn)行初始化,通過在 aar 包中添加一個(gè)自定義的 ContentProvider谜诫,在其 onCreate() 方法中實(shí)現(xiàn)初始化邏輯漾峡。
除了通過 get() 方法,也可以通過 Picasso 的靜態(tài)內(nèi)部類 Builder 來自行構(gòu)建喻旷,主要提供了如下方法:

// 設(shè)置 Bitmap.Config
public Builder defaultBitmapConfig(@NonNull Bitmap.Config bitmapConfig)
// 下載圖片的方式生逸,默認(rèn)使用 OkHttp3Downloader
Builder downloader(@NonNull Downloader downloader)
// 線程池,默認(rèn)為 PicassoExecutorService
public Builder executor(@NonNull ExecutorService executorService)
// 內(nèi)存緩存且预,默認(rèn)為 LruCache
public Builder memoryCache(@NonNull Cache memoryCache)
// 設(shè)置請求轉(zhuǎn)換器槽袄,只能有一個(gè)
public Builder requestTransformer(@NonNull RequestTransformer transformer)
// 添加請求處理器,可以有多個(gè)
public Builder addRequestHandler(@NonNull RequestHandler requestHandler)

下載器

對下載行為的抽象锋谐,提供 load() 方法來加載圖片并返回一個(gè) okhttp3.Response 對象遍尺,shutdown() 方法用于對資源進(jìn)行情理:

public interface Downloader {

  @NonNull Response load(@NonNull okhttp3.Request request) throws IOException; // 加載圖片

  void shutdown();
}

默認(rèn)只有 OkHttp3Downloader 一個(gè)實(shí)現(xiàn),其內(nèi)部通過 OkHttp 進(jìn)行網(wǎng)絡(luò)請求下載圖片:

@NonNull @Override public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
}

線程池

默認(rèn)使用的線程池為 PicassoExecutorService涮拗,核心線程數(shù)和最大線程數(shù)數(shù)量相同乾戏,默認(rèn)都為 3 個(gè),但可根據(jù)網(wǎng)絡(luò)狀態(tài)進(jìn)行動(dòng)態(tài)調(diào)整:2G 網(wǎng)絡(luò)為 1 個(gè)多搀、3G 網(wǎng)絡(luò)為 2 個(gè)歧蕉、4G網(wǎng)絡(luò)為 3 個(gè)、WIFI 網(wǎng)絡(luò)為 4 個(gè)康铭。工作隊(duì)列為 PriorityBlockingQueue惯退,是一個(gè)無界阻塞隊(duì)列,可以通過自定義 compareTo() 來指定排序規(guī)則:

class PicassoExecutorService extends ThreadPoolExecutor {
  private static final int DEFAULT_THREAD_COUNT = 3;

  PicassoExecutorService() {
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
  }
    
   private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
      implements Comparable<PicassoFutureTask> {
      ...

    @Override
    public int compareTo(PicassoFutureTask other) {
      Picasso.Priority p1 = hunter.getPriority();
      Picasso.Priority p2 = other.hunter.getPriority();

      // High-priority requests are "lesser" so they are sorted to the front.
      // Equal priorities are sorted by sequence number to provide FIFO ordering.
      return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
    }
  }
}

compareTo() 中會首先判斷兩個(gè)請求的優(yōu)先級从藤,如果優(yōu)先級相同催跪,則會接著判斷添加的序號,sequence 是一個(gè)自增的 int 整型夷野,也就是說先入隊(duì)的請求先處理懊蒸,是一種先進(jìn)先出的方式。

內(nèi)存緩存器

Cache 接口提供了基本的對于緩存內(nèi)容的處理方法:

public interface Cache {
  Bitmap get(String key);
  void set(String key, Bitmap bitmap);
  int size();
  int maxSize(); //最大可緩存的字節(jié)數(shù)
  void clear();
  void clearKeyUri(String keyPrefix); //根據(jù)前綴移除對應(yīng)的緩存
}

其有 2 個(gè)實(shí)現(xiàn)類悯搔,NONE 和 LruCache骑丸,其中 NONE 是空實(shí)現(xiàn),LruCache 內(nèi)部是對 android.util.LruCache 的封裝妒貌,最大緩存大小為可用內(nèi)存大小的 1/7通危。

請求轉(zhuǎn)換器

RequestTransformer 可以理解為是 Picasso 向外提供的一個(gè)對請求進(jìn)行轉(zhuǎn)換的接口,用戶可以通過自定義一個(gè)轉(zhuǎn)換器來對生成的 Request 對象進(jìn)行處理灌曙,只能設(shè)置一個(gè)菊碟,默認(rèn)提供的實(shí)現(xiàn)為 IDENTITY 直接返回原 Request 對象,如下所示:

public interface RequestTransformer {

  Request transformRequest(Request request);
  
  RequestTransformer IDENTITY = new RequestTransformer() {
    @Override public Request transformRequest(Request request) {
      return request;
    }
  };
}

請求處理器

RequestHandler 是一個(gè)抽象類:

public abstract class RequestHandler {
    public abstract boolean canHandleRequest(Request data);
    public abstract Result load(Request request, int networkPolicy) throws IOException;
    ...
}

其作用就是在不同場景下加載圖片在刺,例如 Picasso 通過 AssetRequestHandler 來加載 asset 文件夾下的圖片資源逆害,用 NetworkRequestHandler 來加載網(wǎng)絡(luò)圖片資源等头镊,通過遍歷所有的 Handler,并調(diào)用它們的 canHandleRequest(Request data) 方法魄幕,如果返回 true相艇,則表示對應(yīng) Handler 可以處理該請求,則該請求就會交由這個(gè) Handler 處理梅垄。在 Picasso 的構(gòu)造函數(shù)中會添加默認(rèn) Handler 和用戶定義的 Handler:

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
    RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
    Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
  ...
  int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
  int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
  List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
  // 優(yōu)先添加用戶自定義的 handler
  allRequestHandlers.add(new ResourceRequestHandler(context));
  if (extraRequestHandlers != null) {
    allRequestHandlers.addAll(extraRequestHandlers);
  }
  allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
  allRequestHandlers.add(new MediaStoreRequestHandler(context));
  allRequestHandlers.add(new ContentStreamRequestHandler(context));
  allRequestHandlers.add(new AssetRequestHandler(context));
  allRequestHandlers.add(new FileRequestHandler(context));
  allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
  ...
}

請求封裝

這一節(jié)對應(yīng)于序列圖中的步驟 4厂捞、5、6队丝,主要作用就是封裝 Request 對象靡馁。
Picasso 提供了 4 種圖片來源的方法:

public RequestCreator load(@NonNull File file)
public RequestCreator load(@DrawableRes int resourceId)
public RequestCreator load(@Nullable Uri uri)
public RequestCreator load(@Nullable String path)

這里需要注意的是,傳入的參數(shù)不能為空机久,不然會直接拋一個(gè)異常臭墨,因此使用前的判空非常有必要。load() 方法會返回一個(gè) RequestCreator 對象膘盖,通過這個(gè)對象就可以來對請求做一些處理操作胧弛。RequestCreator 內(nèi)部持有一個(gè) Request 對象,所有關(guān)于請求的配置都在 Request 類中侠畔。

占位圖和失敗圖

占位圖支持 resId 和 drawable 兩種结缚,并且互斥,如果同時(shí)設(shè)置兩個(gè)則會拋出異常:

public RequestCreator placeholder(@DrawableRes int placeholderResId)
public RequestCreator noPlaceholder() //不設(shè)置占位圖软棺,與設(shè)置占位圖互斥

標(biāo)簽

public RequestCreator tag(@NonNull Object tag)

對一次請求進(jìn)行標(biāo)記红竭,可用于對請求進(jìn)行暫停、恢復(fù)喘落、取消等操作茵宪。

圖片處理

public RequestCreator fit() //調(diào)整圖片大小為 ImageView 大小
public RequestCreator resize(int targetWidth, int targetHeight)
public RequestCreator centerCrop()
public RequestCreator centerInside()
public RequestCreator onlyScaleDown() //僅在圖片大于 target 大小時(shí)對圖片進(jìn)行縮放處理
public RequestCreator rotate(float degrees) // bitmap 旋轉(zhuǎn)角度
public RequestCreator config(@NonNull Bitmap.Config config) // bitmap config
public RequestCreator transform(@NonNull Transformation transformation)  // 對 bitmap 對象做自定義處理
public RequestCreator purgeable() // bitmap復(fù)用
public Builder transform(@NonNull Transformation transformation)

Transformation 用于對加載后的 bitmap 做處理:

public interface Transformation {
  Bitmap transform(Bitmap source);
  String key();
}

緩存策略

public RequestCreator stableKey(@NonNull String stableKey) // 設(shè)置緩存 key 值,擁有相同 key 值的資源將被視為是相同資源
public RequestCreator memoryPolicy(@NonNull MemoryPolicy policy, @NonNull MemoryPolicy... additional) // 內(nèi)存緩存策略
public RequestCreator networkPolicy(@NonNull NetworkPolicy policy, @NonNull NetworkPolicy... additional) //磁盤緩存策略

MemoryPolicy 用于指定內(nèi)存緩存的策略:

public enum MemoryPolicy {
  NO_CACHE(1 << 0), //請求時(shí)跳過內(nèi)存緩存中查詢
  NO_STORE(1 << 1); //請求后跳過緩存到內(nèi)存中
  ...
}

NetworkPolicy 用于指定磁盤緩存的策略瘦棋,而 Picasso 中磁盤緩存是基于 OkHttp 的緩存來實(shí)現(xiàn):

public enum NetworkPolicy {
  NO_CACHE(1 << 0), //跳過從磁盤緩存查詢稀火,直接通過網(wǎng)絡(luò)獲取資源
  NO_STORE(1 << 1), //跳過寫入到磁盤緩存
  OFFLINE(1 << 2); //跳過從網(wǎng)絡(luò)獲取,直接從磁盤緩存中獲取
}

優(yōu)先級

public RequestCreator priority(@NonNull Priority priority) // 請求優(yōu)先級赌朋,用于對請求任務(wù)進(jìn)行排序
public enum Priority {
  LOW,
  NORMAL,
  HIGH
}

用于在前文說到的線程池阻塞隊(duì)列進(jìn)行請求任務(wù)排序凰狞。

加載動(dòng)畫

public RequestCreator noFade() // 加載圖片到 ImageView 時(shí)不顯示漸變動(dòng)畫

// com.squareup.picasso.PicassoDrawable#draw
@Override public void draw(Canvas canvas) {
  if (!animating) {
    super.draw(canvas);
  } else {
    float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;
    if (normalized >= 1f) {
      animating = false;
      placeholder = null;
      super.draw(canvas);
    } else {
      if (placeholder != null) {
        placeholder.draw(canvas);
      }
      // setAlpha will call invalidateSelf and drive the animation.
      int partialAlpha = (int) (alpha * normalized);
      super.setAlpha(partialAlpha);
      super.draw(canvas);
      super.setAlpha(alpha);
    }
  }
}

PicassoDrawable 繼承自 BitmapDrawable,通過重寫 onDraw() 方法實(shí)現(xiàn)了漸變過渡動(dòng)畫沛慢。

請求處理

對應(yīng)于序列圖中步驟 7 以后服球。

請求入隊(duì)

into() 中,首先會通過 createRequest() 來創(chuàng)建一個(gè) Request 請求對象颠焦,然后通過 createKey() 來創(chuàng)建請求的 key 值,這里 key 值的創(chuàng)建通過 stableKey往枣、uri伐庭、rotationDegrees粉渠、resize、centerCrop 等屬性組合而成圾另,因此即使請求的是同個(gè)資源霸株,如果其中有任何一個(gè)屬性有變化,由于生成的 key 值不同集乔,也會重新去請求去件。

public void into(ImageView target, Callback callback) {
    ...
    Request request = createRequest(started);
    String requestKey = createKey(request);
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);
        setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }
    ...
    Action action = new ImageViewAction(...);
    picasso.enqueueAndSubmit(action);
}

如果沒有從內(nèi)存中讀取到,會先創(chuàng)建一個(gè) Action 對象扰路,Action 是一個(gè)抽象類:

abstract class Action<T> {
    ...
    abstract void complete(Bitmap result, Picasso.LoadedFrom from);
    abstract void error(Exception e);
    ...
}

它有幾個(gè)實(shí)現(xiàn)類:FetchAction尤溜、GetAction、ImageViewAction 等汗唱,分別對應(yīng)了 fetch()宫莱、get()into(ImageView) 方法哩罪。接著會將這個(gè) action 對象通過 Picasso.enqueueAndSubmit() 重新又發(fā)送到了 Picasso 中:

void enqueueAndSubmit(Action action) {
  Object target = action.getTarget();
  if (target != null && targetToAction.get(target) != action) {
    // This will also check we are on the main thread.
    cancelExistingRequest(target);
    targetToAction.put(target, action);
  }
  submit(action);
}
void submit(Action action) {
  dispatcher.dispatchSubmit(action);
}

請求發(fā)送

這里 action 對象最終會被傳入到 Dispatcher#performSubmit() 中:

//com.squareup.picasso.Dispatcher#performSubmit()
void performSubmit(Action action, boolean dismissFailed) {
  //如果該請求已經(jīng)暫停授霸,則加入到 pausedActions 中
  if (pausedTags.contains(action.getTag())) {
    pausedActions.put(action.getTarget(), action);
    return;
  }
  BitmapHunter hunter = hunterMap.get(action.getKey());
  if (hunter != null) {
    hunter.attach(action);
    return;
  }
  //如果線程池已經(jīng)關(guān)閉,則對該請求不做處理  
  if (service.isShutdown()) {
    return;
  }
  hunter = forRequest(action.getPicasso(), this, cache, stats, action);
  //將請求封裝成為 Runnable 對象并發(fā)送到線程池中
  hunter.future = service.submit(hunter);
  hunterMap.put(action.getKey(), hunter);
  ...
}

forRequest() 會返回一個(gè) BitmapHunter 對象际插,它繼承自 Runnable碘耳,當(dāng) run() 執(zhí)行時(shí),會通過 hunt()來獲取 Bitmap框弛,這里就是真正來解析 Bitmap 的地方辛辨,首先還是會去內(nèi)存里讀取,接著會調(diào)用 RequestHandler 的 load() 來加載資源功咒,然后對將加載得到的 bitmap 進(jìn)行處理愉阎,具體如何處理則取決于之前在 RequestCreator 中所設(shè)置,這一步處理完成后力奋,還要對用戶自定義的 Transformation 來進(jìn)行榜旦,簡化后的代碼流程如下:

Bitmap hunt() throws IOException {
  Bitmap bitmap = null;
  //從內(nèi)存緩存讀取
  if (shouldReadFromMemoryCache(memoryPolicy)) {
    bitmap = cache.get(key);
  }
  //從 requestHandler 加載
  RequestHandler.Result result = requestHandler.load(data, networkPolicy);
  if (bitmap != null) {
    // 對 bitmap 做處理,如 fit
    if (data.needsTransformation() || exifOrientation != 0) {
      synchronized (DECODE_LOCK) {
        if (data.needsMatrixTransform() || exifOrientation != 0) {
          bitmap = transformResult(data, bitmap, exifOrientation);
        }
        //用戶自定義 bitmap 處理邏輯
        if (data.hasCustomTransformations()) {
          bitmap = applyCustomTransformations(data.transformations, bitmap);
        }
      }
    }
  }
  return bitmap;
}

看下 NetworkRequestHandler 中的 load() 邏輯:

@Override public Result load(Request request, int networkPolicy) throws IOException {
  okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
  Response response = downloader.load(downloaderRequest);
  ResponseBody body = response.body();
  if (!response.isSuccessful()) {
    body.close();
    throw new ResponseException(response.code(), request.networkPolicy);
  }
  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());
  }
  return new Result(body.source(), loadedFrom);
}

private static okhttp3.Request createRequest(Request request, int networkPolicy) {
  CacheControl cacheControl = null;
  if (networkPolicy != 0) {
    if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
      cacheControl = CacheControl.FORCE_CACHE;
    } else {
      CacheControl.Builder builder = new CacheControl.Builder();
      if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
        builder.noCache();
      }
      if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
        builder.noStore();
      }
      cacheControl = builder.build();
    }
  }
  okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(request.uri.toString());
  if (cacheControl != null) {
    builder.cacheControl(cacheControl);
  }
  return builder.build();
}

首先會調(diào)用 createRequest() 來創(chuàng)建一個(gè) Okhttp 的 Request 對象景殷,會根據(jù)之前定義的磁盤緩存邏輯來設(shè)置 Request 的 CacheControl溅呢。接著會將這個(gè)創(chuàng)建好的 Request 對象傳給 OkHttp3Downloader 來調(diào)用 okhttp3.Call.Factory#newCall() 發(fā)送網(wǎng)絡(luò)請求。

請求結(jié)果緩存

通過 BitmapHunter.hunt() 獲取到 Bitmap 后猿挚,會通過 Dispatcher#dispatchComplete() 來回調(diào)結(jié)果到 Dispatcher 中咐旧,在這里會對結(jié)果進(jìn)行緩存處理:

//com.squareup.picasso.Dispatcher#performComplete
void performComplete(BitmapHunter hunter) {
  if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
    cache.set(hunter.getKey(), hunter.getResult());
  }
  hunterMap.remove(hunter.getKey());
  batch(hunter);
}

最終結(jié)果依次被回調(diào)到 ImageViewAction#complete() 或者是 ImageViewAction#error() 中來分別處理加載成功和失敗的邏輯,加載成功則將 bitmap 封裝成 PicassoDrawable 后設(shè)置給 ImageView绩蜻,加載失敗則顯示設(shè)置的失敗圖資源铣墨。

總結(jié)

  • 二級緩存,LruCache 的內(nèi)存緩存和基于 Okhttp Cache 的磁盤緩存办绝,內(nèi)存緩存只會緩存最終處理后的 bitmap伊约,不會對原 bitmap 也進(jìn)行緩存

  • 線程池線程數(shù)可根據(jù)網(wǎng)絡(luò)狀態(tài)進(jìn)行動(dòng)態(tài)切換

  • 不支持 Gif 格式加載

  • 記錄緩存命中率

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姚淆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子屡律,更是在濱河造成了極大的恐慌腌逢,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件超埋,死亡現(xiàn)場離奇詭異搏讶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)霍殴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門媒惕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人繁成,你說我怎么就攤上這事吓笙。” “怎么了巾腕?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵面睛,是天一觀的道長。 經(jīng)常有香客問我尊搬,道長叁鉴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任佛寿,我火速辦了婚禮幌墓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冀泻。我一直安慰自己常侣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布弹渔。 她就那樣靜靜地躺著胳施,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肢专。 梳的紋絲不亂的頭發(fā)上舞肆,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音博杖,去河邊找鬼椿胯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛剃根,可吹牛的內(nèi)容都是我干的哩盲。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼种冬!你這毒婦竟也來了镣丑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤娱两,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后金吗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體十兢,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年摇庙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旱物。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卫袒,死狀恐怖宵呛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情夕凝,我是刑警寧澤宝穗,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站码秉,受9級特大地震影響逮矛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜转砖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一须鼎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧府蔗,春花似錦晋控、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至模捂,卻和暖如春捶朵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狂男。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工综看, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岖食。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓红碑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子析珊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

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