Glide 4.11.0 圖片加載流程深入分析源碼

從Glide圖片加載流程深入分析源碼

不知道有沒有小伙伴跟我一樣,使用Glide已經(jīng)有好多年辜限,平時看源碼比較零碎皇拣,對Glide的源碼只能算一知半解,面試遇到分析Glide的源碼薄嫡,無從說起……

那今天就從0開始氧急,對Glide源碼進(jìn)行一個整體的梳理,更進(jìn)一步的了解Glide的原理毫深。

注意:此文章基于Glide 4.11.0版本

眾所周知吩坝,我們使用Gilde加載圖片時,最常用的一行代碼:

Glide.with(context).load(url).into(imageView)

這里就只先分析此行代碼的主線流程哑蔫,省略了其他的配置方法钉寝。

1. Glide.with()

首先從Glide的with方法開始,它是我們最開始調(diào)用的方法闸迷,有多個重載:

with.png

無論是哪個重載方法嵌纲,內(nèi)部都是return了一行 getRetriever().get() 代碼:

@NonNull
public static RequestManager with(@NonNull Activity activity) {
  return getRetriever(activity).get(activity);
}

先調(diào)用 getRetriever 返回了一個 RequestManagerRetriever 對象,然后在調(diào)用 RequestManagerRetrieverget 方法返回一個RequestManager 對象

RequestManagerRetrieverget方法腥沽,也是有多個重載:

get.png

其中最大的區(qū)別就是在于context和非context的參數(shù)的方法逮走,實(shí)現(xiàn)各不相同:

如果是非Context的參數(shù),比如 activity巡球、fragment言沐、view,其中fragmentview都可以找到一個所屬的Activity對象酣栈,那么最后就會調(diào)用到get(Activity activity)险胰,并內(nèi)部調(diào)用fragmentGet方法:

public RequestManager get(@NonNull Activity activity) {
  if (Util.isOnBackgroundThread()) {
    return get(activity.getApplicationContext());
  } else if (activity instanceof FragmentActivity) {
    return get((FragmentActivity) activity);
  } else {
    assertNotDestroyed(activity);
    frameWaiter.registerSelf(activity);
    android.app.FragmentManager fm = activity.getFragmentManager();
    return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
  }
}

fragmentGet方法中,創(chuàng)建了一個Fragment和RequestManager:

private RequestManager fragmentGet(
    @NonNull Context context,
    @NonNull android.app.FragmentManager fm,
    @Nullable android.app.Fragment parentHint,
    boolean isParentVisible) {
  RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
  RequestManager requestManager = current.getRequestManager();
  if (requestManager == null) {
    Glide glide = Glide.get(context);
    requestManager =
        factory.build(
            glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
    // ...
  }
  return requestManager;
}

這里稍微提一下RequestManagerFragment,getRequestManagerFragment創(chuàng)建的RequestManagerFragment是Glide用來進(jìn)行圖片加載的生命周期管理的矿筝,最終通過FragmentManager添加到Activity上起便,它本身什么都不展示,只是一個空的Fragment,僅僅是通過Fragment的生命周期來管理圖片加載的請求榆综,避免內(nèi)存泄漏問題妙痹,可以看getRequestManagerFragment方法的實(shí)現(xiàn):

private RequestManagerFragment getRequestManagerFragment(
    @NonNull final android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint) {
  RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
  if (current == null) {
    current = pendingRequestManagerFragments.get(fm);
    if (current == null) {
      current = new RequestManagerFragment();
      current.setParentFragmentHint(parentHint);
      pendingRequestManagerFragments.put(fm, current);
      fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
      handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
    }
  }
  return current;
}

回到主線,我們到這里就從RequestManagerRetriever中獲得了一個RequestManager對象鼻疮,從而就可以開始第二步動作:load

2. Glide.with().load()

當(dāng)我們獲得一個RequestManager后怯伊,就可以接著調(diào)用.load()的方法,現(xiàn)在的調(diào)用就就到了Glide.with().load()判沟,可以看到load也是有多個重載方法耿芹,可以支持各種數(shù)據(jù)的load,包括file挪哄、string的url吧秕,uri等等……

load.png

load內(nèi)部是通過調(diào)用asDrawable方法,asDrawable再調(diào)用了as方法迹炼,創(chuàng)建了一個RequestBuilder并返回:

asDrawable()

public RequestBuilder<Drawable> asDrawable() {
  return as(Drawable.class);
}

as()

public <ResourceType> RequestBuilder<ResourceType> as(
    @NonNull Class<ResourceType> resourceClass) {
  return new RequestBuilder<>(glide, this, resourceClass, context);
}

Glide.with().load().into()

這就是我們調(diào)用load方法之后得到RequestBuilder的由來砸彬,得到RequestBuilder之后,按照邏輯順序調(diào)用RequestBuilderinto方法斯入,也是主線邏輯里面最重要的部分:

private <Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> options,
    Executor callbackExecutor) {
  // ...
  // 構(gòu)建Request請求
  Request request = buildRequest(target, targetListener, options, callbackExecutor);

  Request previous = target.getRequest();
  if (request.isEquivalentTo(previous)
      && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
  // ...
    return target;
  }

  requestManager.clear(target);
  target.setRequest(request);
  // 執(zhí)行加載請求
  requestManager.track(target, request);

  return target;
}

這里省略了不相關(guān)的代碼砂碉,into方法中,主要是構(gòu)建了一個Request對象咱扣,然后調(diào)用requestManager.track(target, request)開始加載圖片绽淘,繼續(xù)跟蹤到requestManager.track方法,位于RequestManager.java中:

synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
  targetTracker.track(target);
  requestTracker.runRequest(request);
}

可以看到request繼續(xù)傳遞到requestTracker.runRequest(request)中闹伪,可以繼續(xù)跟蹤看看:

public void runRequest(@NonNull Request request) {
  requests.add(request);
  if (!isPaused) {
    request.begin();
  } else {
    // ...
  }
}

RequestTracker.java類里面,runRequest調(diào)用了request.begain()方法

由于Request是一個接口壮池,這里的request對象在構(gòu)建的時候其實(shí)是構(gòu)建了它的實(shí)現(xiàn)類SingleRequest偏瓤,位于RequestBuilder.java:

private Request obtainRequest(
    Object requestLock,
    Target<TranscodeType> target,
    RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> requestOptions,
    RequestCoordinator requestCoordinator,
    TransitionOptions<?, ? super TranscodeType> transitionOptions,
    Priority priority,
    int overrideWidth,
    int overrideHeight,
    Executor callbackExecutor) {
return SingleRequest.obtain(
      context,
      glideContext,
      requestLock,
      model,
      transcodeClass,
      requestOptions,
      overrideWidth,
      overrideHeight,
      priority,
      target,
      targetListener,
      requestListeners,
      requestCoordinator,
      glideContext.getEngine(),
      transitionOptions.getTransitionFactory(),
      callbackExecutor);
}

那么在SingleRequestbegin方法里面,判斷語句有好幾個椰憋,千萬要迷路厅克,這里的重點(diǎn)是onSizeReady

public void begin() {
  synchronized (requestLock) {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    // ...
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }
  // ...
  }
}

繼續(xù)跟蹤onSizeReady,里面使用engine對象調(diào)用了load方法:

public void onSizeReady(int width, int height) {
  stateVerifier.throwIfRecycled();
  synchronized (requestLock) {
    // ...
    loadStatus =
        engine.load(
            glideContext,
            model,
            requestOptions.getSignature(),
            this.width,
            this.height,
            requestOptions.getResourceClass(),
            transcodeClass,
            priority,
            requestOptions.getDiskCacheStrategy(),
            requestOptions.getTransformations(),
            requestOptions.isTransformationRequired(),
            requestOptions.isScaleOnlyOrNoTransform(),
            requestOptions.getOptions(),
            requestOptions.isMemoryCacheable(),
            requestOptions.getUseUnlimitedSourceGeneratorsPool(),
            requestOptions.getUseAnimationPool(),
            requestOptions.getOnlyRetrieveFromCache(),
            this,
            callbackExecutor);
    // ...
  }
}

繼續(xù)分析Engine的load方法橙依,這里加載請求就揭開了Gilde的緩存機(jī)制:

public <R> LoadStatus load(
    GlideContext glideContext,
    Object model,
    Key signature,
    int width,
    int height,
    Class<?> resourceClass,
    Class<R> transcodeClass,
    Priority priority,
    DiskCacheStrategy diskCacheStrategy,
    Map<Class<?>, Transformation<?>> transformations,
    boolean isTransformationRequired,
    boolean isScaleOnlyOrNoTransform,
    Options options,
    boolean isMemoryCacheable,
    boolean useUnlimitedSourceExecutorPool,
    boolean useAnimationPool,
    boolean onlyRetrieveFromCache,
    ResourceCallback cb,
    Executor callbackExecutor) {
  long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

  // 構(gòu)建加載資源的key
  EngineKey key =
      keyFactory.buildKey(
          model,
          signature,
          width,
          height,
          transformations,
          resourceClass,
          transcodeClass,
          options);

  EngineResource<?> memoryResource;
  synchronized (this) {
    // 從內(nèi)存中加載
    memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

    if (memoryResource == null) {
    // 內(nèi)存中沒有就等待或者是開始新的加載
      return waitForExistingOrStartNewJob(
          glideContext,
          model,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          options,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache,
          cb,
          callbackExecutor,
          key,
          startTime);
    }
  }

  cb.onResourceReady(
      memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
  return null;
}

首先Glide是 loadFromMemory 從內(nèi)存中加載資源证舟,這里的內(nèi)存細(xì)分一下,分別是 活動資源內(nèi)存緩存窗骑,Glide是優(yōu)先從活動資源中獲取資源女责,獲取不到再從內(nèi)存緩存中獲取,如果內(nèi)存緩存也獲取不到创译,loadFromMemory 就返回null:

private EngineResource<?> loadFromMemory(
    EngineKey key, boolean isMemoryCacheable, long startTime) {
  // 檢查是否從內(nèi)存緩存中讀取
  if (!isMemoryCacheable) {
    return null;
  }

  // 從活動緩存中讀取
  EngineResource<?> active = loadFromActiveResources(key);
  if (active != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from active resources", startTime, key);
    }
  // 從活動緩存中讀取到了資源
    return active;
  }

// 從活動緩存中沒有讀取到資源
// 從內(nèi)存緩存中讀取
  EngineResource<?> cached = loadFromCache(key);
  if (cached != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from cache", startTime, key);
    }
    // 從內(nèi)存緩存中讀取讀取到了資源
    return cached;
  }
  return null;
} 

這里從內(nèi)存緩存中讀取到了資源之后抵知,還把資源加入到活動緩存,這樣做的目的是為了下一次獲取數(shù)據(jù)更快(先從活動資源獲取數(shù)據(jù)):

private EngineResource<?> loadFromCache(Key key) {
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire();
    activeResources.activate(key, cached);
  }
  return cached;
}

現(xiàn)在回過頭來看 waitForExistingOrStartNewJob 方法,這個方法是從 loadFromMemory 沒有加載到數(shù)據(jù)時才調(diào)用刷喜,waitForExistingOrStartNewJob 里面構(gòu)建了 engineJobdecodeJob 的對象残制,engineJob 對象用來啟動了一個 decodeJobengineJob 很簡單掖疮,就是維護(hù)了線程池初茶,進(jìn)行線程的調(diào)度,這里在start 里面執(zhí)行了 decodeJob

public synchronized void start(DecodeJob<R> decodeJob) {
  this.decodeJob = decodeJob;
  GlideExecutor executor =
      decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
  executor.execute(decodeJob);
}

由此看來浊闪,既然是線程調(diào)度恼布,decodeJob 是被執(zhí)行的部分,那DecodeJob類肯定是實(shí)現(xiàn)了 Runnable 接口规揪,并將主要的邏輯寫到了 run 方法里面桥氏,跟蹤代碼一看,果不其然:

class DecodeJob<R>
    implements DataFetcherGenerator.FetcherReadyCallback,
        Runnable,
        Comparable<DecodeJob<?>>,
        Poolable {
      // ...
}

避免干擾猛铅,我將run方法里面的注釋和不相關(guān)的邏輯給刪除掉了:

public void run() {
  // ...
  try {
    // ...
    // 主要代碼字支,我們繼續(xù)跟蹤這個方法
    runWrapped();
  } catch (CallbackException e) {
    throw e;
  } catch (Throwable t) {
    // ...
  } finally {
    // ...
  }
}

繼續(xù)跟蹤runWrapped方法:

private void runWrapped() {
  switch (runReason) {
    case INITIALIZE:
  // 首次初始化并獲取資源
      stage = getNextStage(Stage.INITIALIZE);
      currentGenerator = getNextGenerator();
      runGenerators();
      break;
    case SWITCH_TO_SOURCE_SERVICE:
  // 從磁盤緩存獲取不到數(shù)據(jù),重新獲取
      runGenerators();
      break;
    case DECODE_DATA:
  // 獲取資源成功奸忽,解碼數(shù)據(jù)
      decodeFromRetrievedData();
      break;
    default:
      throw new IllegalStateException("Unrecognized run reason: " + runReason);
  }
}

這里通過我的注釋應(yīng)該很容易理解這個switch語句了堕伪,至于獲取圖片成功后的 decodeFromRetrievedData,暫時停一下稍候分析栗菜,其他的兩個case語句欠雌,不管是 INITIALIZE 初始化還是 SWITCH_TO_SOURCE_SERVICE 從磁盤緩存獲取不到數(shù)據(jù)進(jìn)行重試,都是要調(diào)用 runGenerators 來獲取數(shù)據(jù)疙筹,我們繼續(xù)跟蹤看看這個方法:

private void runGenerators() {
  //...
  // 條件語句中的 currentGenerator.startNext() 才是重點(diǎn)
  while (!isCancelled
      && currentGenerator != null
      && !(isStarted = currentGenerator.startNext())) {

    stage = getNextStage(stage);
    currentGenerator = getNextGenerator();

    if (stage == Stage.SOURCE) {
      reschedule();
      return;
    }
  }
  // ...
}

首先看while的條件語句:

  • !isCancelled :如果沒有取消請求
  • currentGenerator != null : currentGenerator對象不為空
  • !(isStarted = currentGenerator.startNext()) : currentGenerator.startNext()的執(zhí)行結(jié)果會賦值給 isStarted

前兩個不過多解釋富俄,主要是第三個語句:如果isStarted的值為false,表示沒有執(zhí)行成功而咆,就會執(zhí)行while語句體里面的內(nèi)容霍比,while語句體里面就會獲取下一個 Stage,以及通過Stage 來獲取對應(yīng)的 Generator

private Stage getNextStage(Stage current) {
  switch (current) {
    case INITIALIZE:
      return diskCacheStrategy.decodeCachedResource()
          ? Stage.RESOURCE_CACHE
          : getNextStage(Stage.RESOURCE_CACHE);
    case RESOURCE_CACHE:
      return diskCacheStrategy.decodeCachedData()
          ? Stage.DATA_CACHE
          : getNextStage(Stage.DATA_CACHE);
    case DATA_CACHE:
      // Skip loading from source if the user opted to only retrieve the resource from cache.
      return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
    case SOURCE:
    case FINISHED:
      return Stage.FINISHED;
    default:
      throw new IllegalArgumentException("Unrecognized stage: " + current);
  }
}
private DataFetcherGenerator getNextGenerator() {
  switch (stage) {
    case RESOURCE_CACHE:
      return new ResourceCacheGenerator(decodeHelper, this);
    case DATA_CACHE:
      return new DataCacheGenerator(decodeHelper, this);
    case SOURCE:
      return new SourceGenerator(decodeHelper, this);
    case FINISHED:
      return null;
    default:
      throw new IllegalStateException("Unrecognized stage: " + stage);
  }
}

根據(jù)兩個方法聯(lián)合分析暴备,getNextGenerator 執(zhí)行結(jié)果:

  • 第一次返回 ResourceCacheGenerator
  • 第二次返回 DataCacheGenerator
  • 第三次的返回取決于 getNextStage 方法中 case DATA_CACHE返回 Stage.FINISHEDStage.SOURCE悠瞬,getNextGenerator 方法就返回 SourceGenerator 或者是 null

那其實(shí)這三個Generator:ResourceCacheGeneratorDataCacheGenerator涯捻、SourceGenerator浅妆,都是實(shí)現(xiàn)自DataFetcherGenerator接口,目的是為了從不同的Generator中加載資源障癌,如果加載成功凌外,就將資源返回,如果加載不成功混弥,就繼續(xù)找下一個Generator加載

不管是從哪個Generator加載數(shù)據(jù)趴乡,都是通過剛才的while語句中的startNext來執(zhí)行对省,不同的Generator內(nèi)部實(shí)現(xiàn)不一樣,但是都大同小異晾捏,都是加載數(shù)據(jù)蒿涎,加載結(jié)果是通過一個叫做FetcherReadyCallback的接口進(jìn)行回調(diào),這里隨便看一個惦辛,DataCacheGeneratorstartNext

public boolean startNext() {
  // ...
  loadData = null;
  boolean started = false;
  while (!started && hasNextModelLoader()) {
    ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
    loadData =
        modelLoader.buildLoadData(
            cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
    if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
      started = true;
  // 加載資源重點(diǎn)代碼
      loadData.fetcher.loadData(helper.getPriority(), this);
    }
  }
  return started;
}

加載資源的重點(diǎn)代碼是:loadData.fetcher.loadData(helper.getPriority(), this)

loadData.fetcher 獲得的是一個DataFetcher劳秋,DataFetcher是獲取資源的接口,所以loadData具體的工作其實(shí)是交給它的實(shí)現(xiàn)類來完成胖齐,它的實(shí)現(xiàn)類有HttpUrlFetcher玻淑、LocalUriFetcherFileFetcher呀伙、AssetPathFetcher等等补履,用于支持各種資源的獲取。

至于加載的細(xì)節(jié)剿另,這里舉兩個例子:

  1. 從http url加載
public void loadData(
    @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
  long startTime = LogTime.getLogTime();
  try {
    InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
    callback.onDataReady(result);
  } catch (IOException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "Failed to load data for url", e);
    }
    callback.onLoadFailed(e);
  } finally {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
    }
  }
}
  1. 從文件加載
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {
  try {
    data = opener.open(file);
    callback.onDataReady(data);
  } catch (FileNotFoundException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "Failed to open file", e);
    }
    callback.onLoadFailed(e);
  }
}

加載成功箫锤,通過callback.onDataReady(data)進(jìn)行回調(diào),加載不成功雨女,則通過callback.onLoadFailed(e)進(jìn)行回調(diào)谚攒。

以上就是Glide加載圖片的主要流程代碼分析,因?yàn)槭鞘÷粤舜蟛糠值脑创a和注釋氛堕,只保留了主線代碼馏臭,所以還是要自己去跟蹤一遍,才更能理解其中的原理讼稚,謝謝觀看括儒!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锐想,隨后出現(xiàn)的幾起案子塑崖,更是在濱河造成了極大的恐慌,老刑警劉巖痛倚,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異澜躺,居然都是意外死亡蝉稳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門掘鄙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耘戚,“玉大人,你說我怎么就攤上這事操漠∈战颍” “怎么了饿这?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長撞秋。 經(jīng)常有香客問我长捧,道長,這世上最難降的妖魔是什么吻贿? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任串结,我火速辦了婚禮,結(jié)果婚禮上舅列,老公的妹妹穿的比我還像新娘肌割。我一直安慰自己,他們只是感情好帐要,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布把敞。 她就那樣靜靜地躺著,像睡著了一般榨惠。 火紅的嫁衣襯著肌膚如雪奋早。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天冒冬,我揣著相機(jī)與錄音伸蚯,去河邊找鬼。 笑死简烤,一個胖子當(dāng)著我的面吹牛剂邮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播横侦,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼挥萌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枉侧?” 一聲冷哼從身側(cè)響起引瀑,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榨馁,沒想到半個月后憨栽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翼虫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年屑柔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片珍剑。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡掸宛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出招拙,到底是詐尸還是另有隱情唧瘾,我是刑警寧澤措译,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站饰序,受9級特大地震影響领虹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜菌羽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一掠械、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧注祖,春花似錦猾蒂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至罩缴,卻和暖如春蚊逢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背箫章。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工烙荷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人檬寂。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓终抽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親桶至。 傳聞我的和親對象是個殘疾皇子昼伴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

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