Glide源码解析

本文基于Glide版本:

com.github.bumptech.glide:glide:4.11.0

Glide最常用的一行代码如下,也概括了Glide的初始化、加载图片(本地、缓存、网络图片)、绑定显示的流程。本文就从该行代码开启Glide的源码之旅。

Glide.with(this).load(url).into(mainPic);

1-初始化with()

首先是Glide.with()方法,通过该方法主要是通过RequestManagerRetriever获取一个RequestManager对象。RequestManager是处理图片加载过程的具体实现类,后面详细讲述。

调用链路:(1)Glide.with

-->(2)Glide.getRetriever
-->(3)Glide.get
-->(4)Glide.getRequestManagerRetriever
-->(5)RequestManagerRetriever.get
-->(6)RequestManager

(1)Glide.with

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

(2)Glide.getRetriever获取RequestManagerRetriever

private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    //判空检测,判断对象空指针以及Activity/Fragment是否处于活跃状态
    Preconditions.checkNotNull(
        context,
        "You cannot start a load on a not yet attached View or a Fragment where getActivity() "
            + "returns null (which usually occurs when getActivity() is called before the Fragment "
            + "is attached or after the Fragment is destroyed).");
    return Glide.get(context).getRequestManagerRetriever();
}

(3)Glide.get(context)获取Glide的全局单例。这么多逻辑主要是处理用户自定义AppGlideModule的情况。

  • 继承AppGlideModule实现自定义Glide,重写网络加载框架、缓存路径、自定义缓存等定制内容,在class前加上@GlideModule注解
  • 通过Annotation Processer在编译时根据@GlideModule注解来生成自定义Glide的代理类GeneratedAppGlideModuleImpl
  • 通过反射获取代理类的构造方法并实例化返回
  • 若APT方法获取失败则尝试从manifest文件中获取自定义GlideModule。manifest配置自定义module是Glide4.0以前的方式,新版本基本都采用注解APT的方式实现。
  • 若都没有自定义的module,则通过GliderBuilder的工厂模式生成实例。
public static Glide get(@NonNull Context context) {
    if (glide == null) {
        //获取自定义AppGlideModule的代理类GeneratedAppGlideModuleImpl   
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModules(context.getApplicationContext());
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context, annotationGeneratedModule);
        }
      }
    }
    return glide;
}

(4)Glide.getRequestManagerRetriever获取Glide.get方法中初始化的RequestManagerRetriever对象。

public RequestManagerRetriever getRequestManagerRetriever() {
    return requestManagerRetriever;
}

(5)RequestManagerRetriever.get方法根据调用的context及是否主线程返回对应的RequestManager。context为FragmentActivity时调用:

public RequestManager get(@NonNull Context context) {
    if (context == null) {
      throw new IllegalArgumentException("You cannot start a load on a null Context");
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
    //Activity、FragmentActivity情况
      if (context instanceof FragmentActivity) {
        return get((FragmentActivity) context);
      } else if (context instanceof Activity) {
        return get((Activity) context);
      } else if (context instanceof ContextWrapper
          && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }
    //非UI线程情况
    return getApplicationManager(context);
}

如果context是Fragment时调用:

public RequestManager get(@NonNull Fragment fragment) {
    Preconditions.checkNotNull(
        fragment.getContext(),
        "You cannot start a load on a fragment before it is attached or after it is destroyed");
    //非UI线程
    if (Util.isOnBackgroundThread()) {
      return get(fragment.getContext().getApplicationContext());
    } else {
        //context为Fragment情况
      FragmentManager fm = fragment.getChildFragmentManager();
      return supportFragmentGet(fragment.getContext(), fm, fragment, fragment.isVisible());
    }
}

总的来说分三种情况:
@1. 非UI线程
@2. UI线程Activity
@3. UI线程Fragment

@1.先来分析第一种情况,第一种情况较简单,通过getApplicationManager返回一个全局的RequestManager单例,该RequestManager是通过ApplicationLifecycle()构造的,也就是说只能感知application的生命周期,使用时需要注意。

@NonNull
  private RequestManager getApplicationManager(@NonNull Context context) {
    if (applicationManager == null) {
      synchronized (this) {
        if (applicationManager == null) {
          Glide glide = Glide.get(context.getApplicationContext());
          applicationManager =
              factory.build(
                  glide,
                  new ApplicationLifecycle(),
                  new EmptyRequestManagerTreeNode(),
                  context.getApplicationContext());
        }
      }
    }
    return applicationManager;
}

@2.再来看UI线程Activity情况

public RequestManager get(@NonNull FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
        //断言activity未销毁
      assertNotDestroyed(activity);
      //获取FragmentManager
      FragmentManager fm = activity.getSupportFragmentManager();
      //核心!!通过fm向当前activity添加一个SupportRequestManagerFragment实例
      //通过SupportRequestManagerFragment感知activity的生命周期
      return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
}

向当前activity添加一个fragment,并通过持有该fragment的lifecycle实例来感知当前activity的生命周期状态。通过fragment感知生命周期这种套路很多地方都能看到例如LifeCycle。

private RequestManager supportFragmentGet(
      @NonNull Context context,
      @NonNull FragmentManager fm,
      @Nullable Fragment parentHint,
      boolean isParentVisible) {
    SupportRequestManagerFragment current =
        getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
}

@3. UI线程Fragment。第三种情况就不必多说了代码和Activty大部分都是复用的,只不过区别在于获取fm上,通过Fragment.getChildFragmentManager来获取fm,向Fragment添加一个子Fragment。子Fragment 则会通过 ChildFragmentManager 和 Fragment 保持生命周期一致。

这样做的好处是可以感知调用方的生命周期,避免生命周期不一致导致的内存泄漏或者空指针问题。

总结一下Glide.with()初始化过程:

  • (1)初始化Glide并获取Glide实例
    • 先通过APT方式产生的自定义GlideModule实例化
    • 没有则通过manifest方式自定义GlideModule实例化
    • 没有则用GliderBuilder构造默认实例
  • (2)获取Glide的RequestManagerRetriever
  • (3)根据是否UI线程及context类型返回对应的RequestManager

2-加载准备load()

RequestManager.load()函数并未实现图片的加载,而是构造了一个RequestBuilder实例

protected RequestBuilder(
      @NonNull Glide glide,
      RequestManager requestManager,
      Class<TranscodeType> transcodeClass,
      Context context) {
    this.glide = glide;//glide实例
    this.requestManager = requestManager;//RequestManager实例
    this.transcodeClass = transcodeClass;//图片加载类型,默认Drawable
    this.context = context;
    this.transitionOptions = requestManager.getDefaultTransitionOptions(transcodeClass);
    this.glideContext = glide.getGlideContext();
    //@1.初始化监听
    initRequestListeners(requestManager.getDefaultRequestListeners());
    //@2.将设置的参数/默认参数赋值应用
    apply(requestManager.getDefaultRequestOptions());
  }

@1.初始化监听过程则是对于设置了图片加载监听的情况,RequestListener主要包含了两个回调:

  • onLoadFaild加载失败
  • onResourceReady资源获取成功

@2.将设置的参数/默认参数赋值应用。这里的参数是在调用时用户传入或者默认参数,通过RequestOptions来保存这些配置,包括图片的裁剪方式、兜底图、出场动画等配置

3-加载显示图片 into()

完成了with初始和load加载准备工作后,就可以开始获取图片并显示到目标ImageView上了。先从RequestBuilder.into开始,这里的RequestBuilder就是上一步load()生成的实例。

public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    //断言是否UI线程,非UI线程抛出异常
    Util.assertMainThread();
    //判空
    Preconditions.checkNotNull(view);
    BaseRequestOptions<?> requestOptions = this;
    //获取裁剪方式配置。注意这里使用的是clone的方式
    //避免修改原始配置参数,导致相同RequestBuilder加载其他目标时配置被修改过。
    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
     
      switch (view.getScaleType()) {
        case CENTER_CROP:
          requestOptions = requestOptions.clone().optionalCenterCrop();
          break;
        case CENTER_INSIDE:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          requestOptions = requestOptions.clone().optionalFitCenter();
          break;
        case FIT_XY:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case CENTER:
        case MATRIX:
        default:
          // Do nothing.
      }
    //@1.核心!!加载图片
    return into(
        //根据类型封装成对应的ViewTarget
        //分为DrawableImageViewTarget、BitmapImageViewTarget
        //分别调用ImageView的setImageDrawable、setImageBitmap来实现图片加载显示
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
}

@1.加载图片into

private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> options,
      Executor callbackExecutor) {
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    Request request = buildRequest(target, targetListener, options, callbackExecutor);

    Request previous = target.getRequest();
    // 这里做了请求优化处理,避免重复资源请求。同时满足如下两个条件时,直接复用前一个request。
    //1.当前request和前一个request相同
    //2.支持内存缓存cacheable=true或者前一个请求未成功完成isComplete=false
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      //如果前一个request处于非running状态,重新启动该请求
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        previous.begin();
      }
      return target;
    }
    //如果是新请求,更新RequestTracker及TargetTracker
    requestManager.clear(target);
    target.setRequest(request);
    //@2.发送加载请求
    requestManager.track(target, request);

    return target;
}

@2.发送加载请求。RequestManager.track方法

synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
    //存储ViewTarget
    targetTracker.track(target);
    //@3.将request加入请求集合并执行
    requestTracker.runRequest(request);
}

这里涉及到的两个比较重要的类RequestTracker及TargetTracker。分别维护请求集合和加载目标集合

  • TargetTracker比较好理解,主要维护了同一个context下所有ViewTarget的集合,并将context的lifecycle的生命周期变化同步给所有ViewTarget,持有的是ViewTarget的弱引用
    • Set<Target<?>> targets =
      Collections.newSetFromMap(new WeakHashMap<Target<?>, Boolean>());
  • RequestTracker是管理当前context下所有的图片加载请求任务Request。包含两个集合,requests保存待发起请求,pendingRequest存放中断状态下的请求。pendingRequest是用强引用List存储,便于恢复执行未完成的请求。
    • Set<Request> requests =
      Collections.newSetFromMap(new WeakHashMap<Request, Boolean>());
    • List<Request> pendingRequests = new ArrayList<>();

@3.将request加入请求集合并执行。RequestTracker.runRequest

public void runRequest(@NonNull Request request) {
    //加入待执行请求集合requests
    requests.add(request);
    if (!isPaused) {
        //@4.非中断状态,请求执行
      request.begin();
    } else {
        //中断状态,请求状态清空并存入pendingRequests
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      pendingRequests.add(request);
    }
}

isPaused状态依赖于context的lifecycle状态。若是ApplicationLifeCyle则app正常运行状态下isPaused=false;若是Fragment/Activity则在其LifeCycle对应的活跃状态下isPaused=false,非活跃状态isPaused=true。

@4.request.begin请求任务的执行,Request只是一个接口,这里具体实现类是SingleRequest。加载任务主要分两步,第一步测量加载尺寸,第二步进行加载

public void begin() {
    synchronized (requestLock) {
      assertNotCallingCallbacks();
      stateVerifier.throwIfRecycled();
      startTime = LogTime.getLogTime();
      。。。//省略

      status = Status.WAITING_FOR_SIZE;
      //@2.若指定了宽高,且尺寸有效测量完毕
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
      } else {
        //未指定宽高,则测量绑定ImageView的宽高,然后还是走到onSizeReady方法
        //同时这里还会通过ViewTreeObserver监听ImageView的尺寸变化,
        //尺寸变化时通知调整图片尺寸
        target.getSize(this);
      }
      //@1.回调ViewTarget的onLoadStarted方法,并返回占位图
      if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
          && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
      }
      。。。
    }
}

@1.ImageViewTarget.onLoadStarted方法中加载占位图,方法比较简单

@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
    super.onLoadStarted(placeholder);
    setResourceInternal(null);
    setDrawable(placeholder);
}

@2.图片尺寸测量完毕后就开始加载图片了,下SingleRequest.onSizeReady方法中主要是通过调用Engine.load方法来加载图片。Engine即图片加载引擎。调用链SingleRequest.onSizeReady-->Engine.load

public <R> LoadStatus load(。。。//入参省略) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    。。。//省略

    EngineResource<?> memoryResource;
    synchronized (this) {
        //@3.从内存/硬盘缓存加载
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      if (memoryResource == null) {
        //@4.缓存中没有,则通过引擎加载
        return waitForExistingOrStartNewJob(。。。//入参省略);
      }
    }

    //@5.回调ResourceCallback.onResourceReady方法,获取图片资源完成
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
    return null;
}

无论是通过缓存@3还是通过网络下载@4,最终都会通过ResourceCallback.onResourceReady来完成图片获取回调,所以先从@5接着分析完整个流程链路。@3和@4涉及到Glide的核心-三级缓存机制,下节详细讲述。

@5.(1)回调ResourceCallback.onResourceReady

-->(2)SingleRequest.onResourceReady

  • 回调对SingleRequest设置的RequestListener.onResourceReady
  • 构建转场动画并回调taget.onResourceReady,这里的target即之前的DrawableImageViewTarget,回调方法在其父类.onResourceReady


-->(3)ImageViewTarget.onResourceReady

public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
        //调用DrawableImageViewTarget.setResource及maybeUpdateAnimatable
      setResourceInternal(resource);
    } else {
        //若为AnimationDrawable等继承了Animatable接口的Drawable,执行其动画
      maybeUpdateAnimatable(resource);
    }
}


-->(4)DrawableImageViewTarget.setResource

-->(5)ImageView.setImageDrawable

至此整个Glide.with(this).load(url).into(mainPic);流程就结束了,由于是限制在UI线程,也没有线程切换的操作,通过熟悉的ImageView.setImageDrawable将图片展示到界面。

RequestBuilder.inot加载图片过程总结:

  • (1)RequestBuilder.into构建新的请求Request或启动上次相同Request,克隆图片裁剪方式配置到Request
  • (2)RequestManager.track添加ViewTarget到TargetTracker,添加Request到RequestTracker并启动request
  • (3)SingleRequest.begin开启请求
    • 测量阶段,通过用户设置的宽高或获取目标ImageView宽高
    • 测量完毕调用onResourceReady-->Engin.load加载图片
  • (4)Engin.load通过Glide的三级缓存机制先从缓存中获取目标资源,否则通过网络请求资源
  • (5)资源获取完毕调用ImageViewTarget.onResourceReady,在回调中设置Drawable到目标ImageView完成加载显示

4-Glide三级缓存机制

再回到第3节中的Engin.load方法中,@3.从内存/硬盘缓存加载。内存缓存也分了两级:

  • ActiveResources。活跃缓存池,正在使用的EngineResource对象,弱引用缓存。资源释放时缓存进入LruResourceCache
  • LruResourceCache。内存缓存池,LruCache管理的EngineResource缓存。获取缓存成功时,从LruResourceCache移除该缓存,并添加到ActiveResources

key-EngineKey:

EngineKey封装了加载图片的uri、宽高等信息。

value-EngineResource:

EngineResource是个泛型类封装了图片资源(根据ViewTarget的类型可以是Drawable也可以是Bitmap),及引用计数acquired,这个引用计数很关键,缓存被引用时计数+1。当某个图片加载完毕时尝试释放资源时要根据该引用来决定是否可以释放。

private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }
    //@6.活跃缓存-ActiveResources
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }
    //@7.若从活跃缓存池activeResources中获取资源失败
    //则从内存缓存-LruResourceCache中获取
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
}

@6.活跃缓存-ActiveResources。通过持有当前使用的EngineResource对象的弱引用来实现缓存。若命中则active.acquire()引用计数+1。

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

@7.内存缓存-LruResourceCache。通过LruCache实现的图片资源缓存。这个LruResourceCache是在初始化时GlideBuilder默认生成的,也可以设置自定义内存缓存

Glide build(@NonNull Context context) {
。。。//代码省略
    //内存缓存
    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
    //硬盘缓存,具体实现是其DiskLruCacheWrapper,后面会讲到
    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }
。。。//代码省略
}
private EngineResource<?> loadFromCache(Key key) {
    //从LruResourceCache中获取resource,并从该缓存中移除
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();//引用计数+1
      activeResources.activate(key, cached);//存入活跃缓存池
    }
    return cached;
}

若两级内存缓存获取资源失败,再回到第3节中@4处发起获取资源任务waitForExistingOrStartNewJob

private <R> LoadStatus waitForExistingOrStartNewJob(。。。//形参省略) {

    。。。//代码省略
    //构建加载任务
    EngineJob<R> engineJob =
        engineJobFactory.build(。。。//实参省略);
    //构建任务的子任务解析图片任务
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(。。。//实参省略);

    jobs.put(key, engineJob);
    //添加任务回调
    engineJob.addCallback(cb, callbackExecutor);
    //@8.通过线程池开启任务
    engineJob.start(decodeJob);

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

@8.通过线程池开启任务。这里开始调用链路太长了,这里按下⏩挑重点来讲吧,避免钻入源码黑洞。。。:

  • -->Engine.waitForExistingOrStartNewJob
  • -->EngineJob.start
  • -->DecodeJob.run-->runWrapped-->runGenerators
  • -->DataFetcherGenerator.start
    • (a)ResourceCacheGenerator.startNext
    • (b)SourceGenerator.startNext

在这里打断一下,开始出现了分支。

(a)硬盘缓存:ResourceCacheGenerator.startNext从硬盘缓存中
。在runGenerators这个函数中出现了硬盘缓存逻辑。Glide默认DiskLruCacheWrapper实现,原理类似DiscLruCache同样使用LRU策略管理缓存文件,且可以通过设置DiskCacheStrategy来决定缓存原图文件还是解码后的图片。

(b)网络加载:若硬盘缓存获取失败,SourceGenerator.startNext通过网络请求下载图片,具体实现是HttpUrlFetcher。HttpURLConnection建立网络连接,下载图片、解码、压缩、并根据缓存策略存储到硬盘缓存中、更新到内存缓存。这部分涉及代码较多就不展开讲了。。。

  • -->DataFetcherGenerator.onDataReady
  • -->DecodeJob.onDataFetcherReady
  • -->EncodeJob.onResourceReady。在这里会更新图片资源到活跃内存缓存activeResources

后面的流程就回到了第3节@5中了,不再赘述

  • -->ResourceCallback.onResourceReady
  • -->SingleRequest.onResourceReady
  • -->ImageViewTarget.onResourceReady
  • -->DrawableImageViewTarget.setResource
  • -->ImageView.setImageDrawable

总结下Glide的三级缓存策略:

  • (1)Engine.load中先从内存缓存中加载图片loadFromMemory
    • 先从actives活跃缓存中获取
    • 获取失败从内存缓存cache中获取,同时将缓存从cache中移到actives中
  • (2)内存缓存获取失败,则构建EngineJob并执行start
  • (3)执行DecodeEngine.run通过ResourceCacheGenerator从硬盘缓存中读取
  • (4)读取失败则通过SourceGenerator执行获取任务,下载网络图片
  • (5)下载成功后经过解码、压缩后,更新硬盘缓存及活跃缓存actives
  • (6)回调ImageViewTarget最终设置并显示图片到目标ImageView

5-总结

最后总结下Glide.with(this).load(url).into(mainPic)这行代码中Glide从初始化到加载显示图片的整个流程:

流程图

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345