Glide源碼分析(三),Engine加載資源過(guò)程

通過(guò)前面的分析利朵,我們知道真正去加載數(shù)據(jù)是在SingleRequest#onSizeReady方法中被觸發(fā)律想,這個(gè)里面是調(diào)用了Engine#load方法,看到這個(gè)方法绍弟,我們大致可以猜到此時(shí)便開(kāi)始去真正加載數(shù)據(jù)了技即,從緩存中讀取或者是從網(wǎng)絡(luò)獲取等等。在開(kāi)始之前樟遣,我們先簡(jiǎn)單了解一下Engine類中涉及到的一些類而叼。
仍然以最簡(jiǎn)單的load方式為例子

   Glide.with(this)
                .load("https://p.upyun.com/docs/cloud/demo.jpg")
                .into(imageView);

關(guān)鍵類

  • Key
    唯一標(biāo)識(shí)一些數(shù)據(jù)的接口。詳細(xì)介紹->Key結(jié)構(gòu)

  • EngineKey
    實(shí)現(xiàn)了Key接口豹悬,用做多路復(fù)用負(fù)載的內(nèi)存緩存鍵

  • Resource
    一個(gè)包裝了特定類型的資源接口葵陵,并且能夠匯集和重用。詳細(xì)介紹->Resource結(jié)構(gòu)

  • MemoryCache
    內(nèi)存緩存接口屿衅,用于在內(nèi)存緩存中添加和移除資源埃难,這里的實(shí)現(xiàn)類是LruResourceCache,繼承了LruCache涤久。存放的是Key和Resource鍵值對(duì)涡尘。詳細(xì)介紹->MemoryCache結(jié)構(gòu)

  • DiskCache
    這里的DiskCache是由InternalCacheDiskCacheFactory創(chuàng)建,其繼承自DiskLruCacheFactory响迂,最終DiskCache的實(shí)現(xiàn)類是DiskLruCacheWrapper對(duì)象考抄。詳細(xì)介紹->DiskCache結(jié)構(gòu)

  • ActiveResources
    存放了已經(jīng)被Request請(qǐng)求的資源,是內(nèi)存緩存的一種蔗彤。

  • ResourceRecycler
    一個(gè)回收Resource的輔助類川梅,防止陷入遞歸疯兼,當(dāng)回收的Resource資源有子資源的時(shí)候。

  • EngineJob
    通過(guò)添加和刪除回調(diào)以進(jìn)行加載并在加載完成時(shí)通知回調(diào)來(lái)管理加載的類

  • DecodeJob
    負(fù)責(zé)從緩存數(shù)據(jù)或原始資源解碼資源并應(yīng)用轉(zhuǎn)換和轉(zhuǎn)碼的類贫途。

  • Jobs
    一個(gè)負(fù)責(zé)緩存EngineJob的管理類吧彪,里面存放了Key與EngineJob的Map對(duì)象。

有了上面的幾把認(rèn)知之后丢早,我們來(lái)看看代碼的實(shí)現(xiàn)姨裸,先分析Engine的構(gòu)造方法,如果覺(jué)得思路很亂怨酝,建議先看本文結(jié)尾的總結(jié)傀缩,宏觀有一個(gè)大致的了解,再來(lái)看這些過(guò)程农猬。

1. Engine#Engine

 public Engine(
      MemoryCache memoryCache,
      DiskCache.Factory diskCacheFactory,
      GlideExecutor diskCacheExecutor,
      GlideExecutor sourceExecutor,
      GlideExecutor sourceUnlimitedExecutor,
      GlideExecutor animationExecutor,
      boolean isActiveResourceRetentionAllowed) {
    this(
        memoryCache,
        diskCacheFactory,
        diskCacheExecutor,
        sourceExecutor,
        sourceUnlimitedExecutor,
        animationExecutor,
        /*jobs=*/ null,
        /*keyFactory=*/ null,
        /*activeResources=*/ null,
        /*engineJobFactory=*/ null,
        /*decodeJobFactory=*/ null,
        /*resourceRecycler=*/ null,
        isActiveResourceRetentionAllowed);
  }

  @VisibleForTesting
  Engine(MemoryCache cache,
      DiskCache.Factory diskCacheFactory,
      GlideExecutor diskCacheExecutor,
      GlideExecutor sourceExecutor,
      GlideExecutor sourceUnlimitedExecutor,
      GlideExecutor animationExecutor,
      Jobs jobs,
      EngineKeyFactory keyFactory,
      ActiveResources activeResources,
      EngineJobFactory engineJobFactory,
      DecodeJobFactory decodeJobFactory,
      ResourceRecycler resourceRecycler,
      boolean isActiveResourceRetentionAllowed) {
    this.cache = cache;
    this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);

    if (activeResources == null) {
      activeResources = new ActiveResources(isActiveResourceRetentionAllowed);
    }
    this.activeResources = activeResources;
    activeResources.setListener(this);

    if (keyFactory == null) {
      keyFactory = new EngineKeyFactory();
    }
    this.keyFactory = keyFactory;

    if (jobs == null) {
      jobs = new Jobs();
    }
    this.jobs = jobs;

    if (engineJobFactory == null) {
      engineJobFactory =
          new EngineJobFactory(
              diskCacheExecutor, sourceExecutor, sourceUnlimitedExecutor, animationExecutor, this);
    }
    this.engineJobFactory = engineJobFactory;

    if (decodeJobFactory == null) {
      decodeJobFactory = new DecodeJobFactory(diskCacheProvider);
    }
    this.decodeJobFactory = decodeJobFactory;

    if (resourceRecycler == null) {
      resourceRecycler = new ResourceRecycler();
    }
    this.resourceRecycler = resourceRecycler;

    cache.setResourceRemovedListener(this);
  }

構(gòu)造方法中赡艰,初始化了部分成員變量和一些構(gòu)造類的工廠,還包括一些輔助的Resource回收的類ResourceRecycler斤葱,其中比較重要的就是Cache慷垮、KeyFactory、EngineJob和DecodeJob苦掘。下面會(huì)看到在load方法中换帜,如何去使用的。

2.Engine#load

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) {
    Util.assertMainThread();
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }

這段代碼第一是通過(guò)keyFactory構(gòu)建一個(gè)EngineKey鹤啡,這個(gè)key信息包含了以下幾個(gè)部分:
model:此時(shí)是圖片url
signature: 額外的數(shù)據(jù)惯驼,能夠和memory與disk的緩存key進(jìn)行再一次混合,當(dāng)緩存失效時(shí)候递瑰,可以進(jìn)行更多的控制祟牲。一般情況下默認(rèn)實(shí)現(xiàn)是EmptySignature。
width/height:加載的尺寸
transformations: 是一個(gè)Map<Class<?>, Transformation<?>>鍵值對(duì)抖部,Transformation也是可以和memory與disk的緩存key進(jìn)行進(jìn)一步混合说贝,添加額外的信息。在此時(shí)它里面放置了以下信息:
Bitmap.class -> FitCenter
Drawable.class -> DrawableTransformation
BitmapDrawable.class -> DrawableTransformation
GifDrawable.class -> GifDrawableTransformation
resourceClass: 此時(shí)為Object.class. 未知
transcodeClass: 指定了需要返回的Resource類型
options:Options類也是一個(gè)實(shí)現(xiàn)了Key的接口慎颗,與ObjectKey略微不同的是它內(nèi)部的數(shù)據(jù)結(jié)構(gòu)是ArrayMap乡恕。此時(shí)ArrayMap存放的是Option -> com.bumptech.glide.load.resource.bitmap.DownsampleStrategy$FitCenter。Option中的成員變量key為'com.bumptech.glide.load.resource.bitmap.Downsampler.DownsampleStrategy'俯萎。
由上述這些對(duì)象共同構(gòu)造一個(gè)EngineKey的對(duì)象傲宜,它是一個(gè)僅在內(nèi)存中使用的緩存key。實(shí)現(xiàn)了equals與hashCode方法夫啊。實(shí)現(xiàn)如下:

  @Override
  public boolean equals(Object o) {
    if (o instanceof EngineKey) {
      EngineKey other = (EngineKey) o;
      return model.equals(other.model)
          && signature.equals(other.signature)
          && height == other.height
          && width == other.width
          && transformations.equals(other.transformations)
          && resourceClass.equals(other.resourceClass)
          && transcodeClass.equals(other.transcodeClass)
          && options.equals(other.options);
    }
    return false;
  }

  @Override
  public int hashCode() {
    if (hashCode == 0) {
      hashCode = model.hashCode();
      hashCode = 31 * hashCode + signature.hashCode();
      hashCode = 31 * hashCode + width;
      hashCode = 31 * hashCode + height;
      hashCode = 31 * hashCode + transformations.hashCode();
      hashCode = 31 * hashCode + resourceClass.hashCode();
      hashCode = 31 * hashCode + transcodeClass.hashCode();
      hashCode = 31 * hashCode + options.hashCode();
    }
    return hashCode;
  }

可以看到函卒,由上面各個(gè)部分,決定了equals和hashCode的結(jié)果撇眯。如果有多次請(qǐng)求报嵌,則可以根據(jù)這些屬性生成的EngineKey緩存key虱咧,若能匹配到,則可以復(fù)用這個(gè)緩存結(jié)果锚国。接下來(lái)就是通過(guò)isMemoryCacheable和key去讀取緩存腕巡。在接下來(lái)loadFromActiveResources中,如果能夠拿到EngineResource跷叉,則整個(gè)過(guò)程結(jié)束逸雹,直接使用內(nèi)存緩存即可,下面我們先分析這個(gè)方法的實(shí)現(xiàn)云挟。

3. Engine#loadFromActiveResources

  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }

這個(gè)方法首先是判斷isMemoryCacheable是否可用,不可用直接就返回null转质,在Engine#load方法中园欣,如果發(fā)現(xiàn)是null,則會(huì)進(jìn)行其他的策略休蟹,比如讀取disk或請(qǐng)求網(wǎng)絡(luò)等等沸枯。如果isMemoryCacheable可用,則從activeResources中去查找赂弓,我們來(lái)看ActiveResources#get實(shí)現(xiàn)绑榴。

 @Nullable
  EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }

activeEngineResources是一個(gè)Map<Key, ResourceWeakReference>對(duì)象,而ResourceWeakReference是WeakReference<EngineResource<?>>的一個(gè)子類盈魁,既然有取值的地方翔怎,那么肯定有對(duì)activeEngineResources put內(nèi)容的時(shí)候,在該類中杨耙,我們發(fā)現(xiàn)其activate方法會(huì)向activeEngineResources中put內(nèi)容赤套。什么時(shí)候回觸發(fā)這個(gè)方法,帶著這個(gè)問(wèn)題我們繼續(xù)分析珊膜,顯然容握,首次加載這里get方法會(huì)返回一個(gè)null,因此loadFromActiveResources的結(jié)果也是一個(gè)null车柠。繼續(xù)回到load方法剔氏,此時(shí)loadFromActiveResources返回為null,則繼續(xù)下一步的策略竹祷,進(jìn)入到loadFromCache方法谈跛。它也是如此,如果能夠取到資源溶褪,則本次load結(jié)束币旧,否則繼續(xù)。

4. Engine#loadFromCache

 private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

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

這個(gè)方法中猿妈,依舊是對(duì)isMemoryCacheable的簡(jiǎn)單判斷吹菱,接下來(lái)是調(diào)用getEngineResourceFromCache方法去讀取巍虫,這里發(fā)現(xiàn),如果能夠取到資源鳍刷,則activate方法被觸發(fā)占遥,這里解決了上面什么時(shí)候去往activeEngineResources中put內(nèi)容的疑問(wèn),當(dāng)然并非只有一處输瓜。至于cached.acquire這個(gè)暫不深究瓦胎,用到了計(jì)數(shù)的概念,acquired大于0時(shí)候尤揣,表明有地方正在使用resource資源搔啊,其實(shí)這里可以看到,它這個(gè)是一種優(yōu)化的策略北戏,節(jié)省了內(nèi)存資源负芋。細(xì)節(jié)往往容易影響主線,我們繼續(xù)分析getEngineResourceFromCache這個(gè)的實(shí)現(xiàn)嗜愈,參數(shù)也是一個(gè)緩存key旧蛾。

private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource<?>) cached;
    } else {
      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }

這里的cache實(shí)現(xiàn)了MemoryCache接口,是我們的內(nèi)存緩存對(duì)象蠕嫁,具體的實(shí)現(xiàn)類是LruResourceCache實(shí)現(xiàn)了LruCache<Key, Resource<?>>锨天,而在LruCache中,維護(hù)了一個(gè)LinkedHashMap<Key, Resource<?> cache對(duì)象剃毒,顯然有對(duì)cache增刪查詢的操作病袄。這里我們后續(xù)分析對(duì)cache put的操作。這里迟赃,顯然cache.remove(key)會(huì)返回一個(gè)null對(duì)象陪拘,因此整個(gè)方法返回值也是null,繼續(xù)跟進(jìn)load代碼纤壁。

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

由于loadFromCache返回依然為空左刽,接下來(lái)就是從jobs中去尋找,是否存在了一個(gè)EngineJob酌媒,它是一個(gè)通過(guò)添加和刪除回調(diào)以進(jìn)行加載并在加載完成時(shí)通知回調(diào)來(lái)管理加載的類欠痴。如果存在,則可以復(fù)用此次的EngineJob秒咨,一個(gè)EngineJob和ResourceCallback是一個(gè)一對(duì)多的關(guān)系喇辽,addCallback方法的實(shí)現(xiàn)如下:

  void addCallback(ResourceCallback cb) {
    Util.assertMainThread();
    stateVerifier.throwIfRecycled();
    if (hasResource) {
      cb.onResourceReady(engineResource, dataSource);
    } else if (hasLoadFailed) {
      cb.onLoadFailed(exception);
    } else {
      cbs.add(cb);
    }
  }

這段代碼很清晰,如果已經(jīng)有資源了雨席,則直接返回菩咨,不需要再去請(qǐng)求,大大得到復(fù)用了。如果失敗抽米,則上報(bào)異常信息特占,否則,則會(huì)添加到cbs變量中云茸,說(shuō)明此時(shí)正在加載是目,會(huì)在之后的加載成功或失敗中,觸發(fā)cbs遍歷去回調(diào)各個(gè)Callback标捺。
首次加載中懊纳,顯然jobs中拿到的EngineJob也是空,因此進(jìn)一步分析load亡容,也就是真正去請(qǐng)求了嗤疯。

5. Enging#load

public <R> LoadStatus load(...{
    ...
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }

真正去請(qǐng)求代碼也很簡(jiǎn)單,主要涉及到EngineJob與DecodeJob萍倡,DecodeJob是一個(gè)負(fù)責(zé)從緩存數(shù)據(jù)或原始資源解碼資源并應(yīng)用轉(zhuǎn)換和轉(zhuǎn)碼的類身弊,它實(shí)現(xiàn)了Runnable接口,是真正加載線程的入口列敲。然后是將engineJob緩存至jobs變量中,最后在EngineJob的start方法中請(qǐng)求線程池去執(zhí)行本次任務(wù)帖汞,至此戴而,加載就已經(jīng)被觸發(fā),后面我們繼續(xù)分析加載的核心邏輯DecodeJob的實(shí)現(xiàn)翩蘸∷猓總的來(lái)說(shuō),加載分為了以下幾個(gè)過(guò)程:

  1. SingleRequest#onSizeReady方法根據(jù)前面RequestBuilder設(shè)置的參數(shù)催首,請(qǐng)求Engine#load方法
  2. Engine#load方法中扶踊,根據(jù)相關(guān)參數(shù),組裝成一個(gè)EngineKey郎任,用于標(biāo)識(shí)此次請(qǐng)求的緩存key秧耗,首先以這個(gè)key去從當(dāng)前還處理激活狀態(tài)的Resource資源中去尋找,若查找成功舶治,則返回分井;否則,進(jìn)入下一階段霉猛。
  3. 若從激活狀態(tài)的Resource資源查找失敗尺锚,則進(jìn)一步去MemoryCache中去查找,若查找成功惜浅,則返回瘫辩;否則,進(jìn)入下一階段。
  4. 若從MemoryCache中查找失敗伐厌,則再?gòu)膉obs中去看是否存在一個(gè)已經(jīng)加載完成或正在加載的EngineJob承绸。若找到,則將回調(diào)設(shè)置到EngineJob以便接收加載成功或失敗的通知弧械;否則八酒,進(jìn)入下一階段。
  5. 若沒(méi)有查找到EngineJob刃唐,則創(chuàng)建一個(gè)EngineJob以及DecodeJob羞迷,同時(shí)加入到j(luò)obs緩存之中,并最終調(diào)用EngineJob#start方法画饥,觸發(fā)加載線程執(zhí)行真正的加載衔瓮,從遠(yuǎn)端獲取或者是磁盤獲取等。

下一篇 Glide源碼分析(四)抖甘,DecodeJob執(zhí)行過(guò)程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末热鞍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衔彻,更是在濱河造成了極大的恐慌薇宠,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艰额,死亡現(xiàn)場(chǎng)離奇詭異澄港,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)柄沮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門回梧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人祖搓,你說(shuō)我怎么就攤上這事狱意。” “怎么了拯欧?”我有些...
    開(kāi)封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵详囤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我哈扮,道長(zhǎng)纬纪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任滑肉,我火速辦了婚禮包各,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘靶庙。我一直安慰自己问畅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著护姆,像睡著了一般矾端。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卵皂,一...
    開(kāi)封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天秩铆,我揣著相機(jī)與錄音,去河邊找鬼灯变。 笑死殴玛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的添祸。 我是一名探鬼主播滚粟,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼刃泌!你這毒婦竟也來(lái)了凡壤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤耙替,失蹤者是張志新(化名)和其女友劉穎亚侠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盖奈,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年狐援,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片究孕。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啥酱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厨诸,到底是詐尸還是另有隱情镶殷,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布微酬,位于F島的核電站绘趋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏颗管。R本人自食惡果不足惜陷遮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垦江。 院中可真熱鬧帽馋,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吧慢,卻和暖如春涛漂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背检诗。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工匈仗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岁诉。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓锚沸,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涕癣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哗蜈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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