2019-10-24

Glide源码分析(一) :代码框架和执行流程

Android平台经过多年的发展,涌现出很多图片加载开源框架, 其中使用量非常大的一个开源框架是Glide, Glide是Google推荐的图片框架,并在自家的项目中大量使用的一个非常强大的框架,Glide的功能强大,不仅支持常规的图片加载,还提供Gif,本地Video首帧的解码和显示。

Glide图片框架是用方式上采用链式调用,对于开发者而言使用起来非常简单,开发者可以非常简单的对图片进行配置和二次加工。但是使用简单的背后是极其复杂的代码框架和细节设计,特别是各种资源类型的相互转化,模块的组装。如果没有了解Glide的框架设计思想,源码的阅读过程中很容易陷入代码的迭代流中,面对陡然升级的代码复杂度和信息量,阅读者很容易产生恐惧和迷茫。

1 设计思想

Glide的设计思想是,把每一部分的功能都封装到一个模块中,在Glide的初始化的时候将这些模块进行初始化,在图片请求和加载的过程中根据相关输入参数从Glide实例对象中取得这些模块,将实际执行逻辑交给这些模块执行。这样做的好处是每个模块代码逻辑清晰,不同任务的模块代码边界清晰,方便框架拓展、增加新的业务模块,上层的调用入口不局限于任何对象, 只要底层存在相关处理模块即可, 这也是 Glide框架强大的所在。Glide的本质是提供了入口对象和底层业务模块的调用框架。

1.1模块设计

ResourceDecoder<T, Z>

ResourceDecoder<T, Z>模块作用是将输入的T类型的资源解码转化成Z类型的结果输出, 例如输入资源类型T为InputStream,解码成的结果Z为Bitmap。Glid源码中资源相互转化的模块很多,我们这里就以最常使用的输入InputStream和输出Bitmap观察一下ResourceDecoder<T, Z>的实现StreamBitmapDecoder,其类结构如下:

ResourceDecoder.png

ResourceDecoder真正实现接口是decode()接口。

DataLoadProvider<T, Z>

DataLoadProvider<T, Z>模块的作用是提供T和Z类型相互转化的Decoder和Encoder,
例如提供上面介绍过的StreamBitmapDecoder,仍然举输入InputStream和输出Bitmap为例,观察一下DataLoadProvider<T, Z>定义和实现:


DataLoadProvider.png

这里我们主要关心getSourceDecoder()接口,其作用就是返回上面提到的StreamBitmapDecoder
StreamBitmapDecoder<T, Z>

DataFetcher<T>

DataFetcher<T>的作用是,根据某一具体资源封装实现返回数据流或者数据流的封装,例如将Http输入转化为InputStream, 以Http业务为例观察其实现:


DataFetcher.png

其实现接口为loadData(),返回InputStream。

ModelLoader<T, Y>

ModelLoader<T, Y>模块的作用是将T类型的资源输入,返回Y类型的DataFetcher,例如将GlideUrl输入转换成创建创建InputStream的HttpUrlFetcher, 其实现结构如下:


ModelLoader.png

其主要实现接口是getResourceFetcher(),返回HttpUrlFetcher。

ModelLoaderFactory<T, Y>

ModelLoaderFactory<T, Y>从字面意义就能看出来器功能和作用,就是创建相关ModleLoader并将创建好的ModelLoader实例注册到Glide中, 定义和实现如下:


ModleLoaderFactory.png

ResourceTranscoder<Z, R>

ResourceTranscoder的做用是将数据流解析的结果转化为另一种类型结果,例如Resource<Bitmap>转化为Resource<GlideDrawable>。以BitmapToGlideDrawableTranscoder为例ResourceTranscoder的实现如下:


ResourceTranscoder.png

Glide在初始化的时候会注册很多业务模块,考虑到这篇文章主要帮助读者理解Glide的设计原理和执行流程,上述模块足够大家理解Glide的设计原理。下面我们进入Glide请求加载资源的流程,看看Glide如何利用上述模块实现图片加载的。

Glide加载图片流程

如果顺着代码的执行流程的每段代码分析下去,由于Glide代码比较复杂,读完一段代码逻辑后,很容易将上一段内容忘记,不容易宏观上了解整套代码的执行流程,采用时序图的方式可以比较直观的了解每一段业务代码所在的时序,并能帮助读者复盘前面分析的代码内容,Glide的整体执行时序如下:


image.png

下面我们Glide加载图片时序图来分析源码,具体分析每一段的代码的逻辑。

Glide构造函数

Glide构造函数中初始化了上面介绍到的各种模块,代码如下:

Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
        。。。。。。
        //DataLoadProvider内存缓存,用于其他模块从Glide中获取DataLoadProvider
        dataLoadProviderRegistry = new DataLoadProviderRegistry();
        //InputStream解码为Bitmap的DataLoadProvider
        StreamBitmapDataLoadProvider streamBitmapLoadProvider =
                new StreamBitmapDataLoadProvider(bitmapPool, decodeFormat);
        dataLoadProviderRegistry.register(InputStream.class, Bitmap.class, streamBitmapLoadProvider);
        //File解码为Bitmap的DataLoadProvider
        FileDescriptorBitmapDataLoadProvider fileDescriptorLoadProvider =
                new FileDescriptorBitmapDataLoadProvider(bitmapPool, decodeFormat);
        dataLoadProviderRegistry.register(ParcelFileDescriptor.class, Bitmap.class, fileDescriptorLoadProvider);
        //封装了StreamBitmapDataLoadProvider和FileDescriptorBitmapDataLoadProvider,支持InputStream和File解码成Bitmap
        ImageVideoDataLoadProvider imageVideoDataLoadProvider =
                new ImageVideoDataLoadProvider(streamBitmapLoadProvider, fileDescriptorLoadProvider);
        dataLoadProviderRegistry.register(ImageVideoWrapper.class, Bitmap.class, imageVideoDataLoadProvider);
        //InputStream解码为GifDrawable的DataLoadProvider
        GifDrawableLoadProvider gifDrawableLoadProvider =
                new GifDrawableLoadProvider(context, bitmapPool);
        dataLoadProviderRegistry.register(InputStream.class, GifDrawable.class, gifDrawableLoadProvider);
         //封装了ImageVideoDataLoadProvider和GifDrawableLoadProvider, 支持InputStream和File转化为GifBitmapWrapper, GifBitmapWrapper封装了GlideDrawable和GifDrawable
        dataLoadProviderRegistry.register(ImageVideoWrapper.class, GifBitmapWrapper.class,
                new ImageVideoGifDrawableLoadProvider(imageVideoDataLoadProvider, gifDrawableLoadProvider, bitmapPool));
         //File转化为ParcelFileDescriptor的ModelLoader工厂类, FileDescriptorFileLoader.中获取
        register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());
        //从File中获取InputStream        
        register(File.class, InputStream.class, new StreamFileLoader.Factory());
         //从资源Id中获取文件资源描述符
        register(int.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
        register(int.class, InputStream.class, new StreamResourceLoader.Factory());
        register(Integer.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
        //从资源Id中获取InputStream, 例如res/drawable中的图片资源
        register(Integer.class, InputStream.class, new StreamResourceLoader.Factory());
        //从String中获取ParcelFileDescriptor
        register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
        register(String.class, InputStream.class, new StreamStringLoader.Factory());
        //从Uri中获取ParcelFileDescriptor
        register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
        //从Uri中获取InputStream
        register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
        //从URL中获取InputStream
        register(URL.class, InputStream.class, new StreamUrlLoader.Factory());
        //从GlideUrl中获取InputStream
        register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
        //将byte字节转化为InpuStream流
        register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());
        //将Btima资源类型转化为GlideBitmapDrawable类型
        transcoderRegistry.register(Bitmap.class, GlideBitmapDrawable.class,
                new GlideBitmapDrawableTranscoder(context.getResources(), bitmapPool));
        //GifBitmapWrapper转化为GlideDrawable资源类型
        transcoderRegistry.register(GifBitmapWrapper.class, GlideDrawable.class,
                new GifBitmapWrapperDrawableTranscoder(
                        new GlideBitmapDrawableTranscoder(context.getResources(), bitmapPool)));
         。。。。。。
    }

Glide构造函数中会初始化DataLoadProvider、ModelLoader工厂类和ResourceTransonder,这些模块将会在接下来的流程被调用。

Glide.with()

Glide.with() 的作用创建RequestManager, RequestManager的作用是管理图片请求的生命周期和创建GenericRequestBuilder, 这里不做过多的描述。

RequestManager.load()

RequestManager.load()是根据输入的资源来源创建相关的从Glide中查找已经注册好的资源模块,然后构造GenericRequestBuilder, 我们就以load()接口输入我String类型的Url地址类为例, 分析一下其核心函数和执行流程:
loadGeneric()方法,代码如下:

private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
        //首先从Glide中查找注册好的ModelLoader
        //modelClass为String.class,  ModelLoader为StreamStringLoader
        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
         //modelClass为String.class,  ModelLoader为FileDescriptorResourceLoader
        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
                Glide.buildFileDescriptorModelLoader(modelClass, context);
        //创建DrawableTypeRequest实例对象,封装了StreamStringLoader, FileDescriptorResourceLoader, Glide等,DrawableTypeRequest
        //DrawableTypeRequest是GenericRequest子类。等于创建一个GenericRequest实例对象。
        return optionsApplier.apply(
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }

下面我们来看一下DrawableTypeRequest构造函数到底做了哪些工作,代码如下:

DrawableTypeRequest(Class<ModelType> modelClass, ModelLoader<ModelType, InputStream> streamModelLoader,
            ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide,
            RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) {
        super(context, modelClass,
                buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class,
                        GlideDrawable.class, null),
                glide, requestTracker, lifecycle);
        this.streamModelLoader = streamModelLoader;
        this.fileDescriptorModelLoader = fileDescriptorModelLoader;
        this.optionsApplier = optionsApplier;
    }

DrawableTypeRequest的构造函数,初始化了父类的构造函数和自身的一些变量,
其中buildProvider()是比较关键的一个方法,其代码如下:

private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide,
            ModelLoader<A, InputStream> streamModelLoader,
            ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader, Class<Z> resourceClass,
            Class<R> transcodedClass,
            ResourceTranscoder<Z, R> transcoder) {
       
        /*从Glide中获取ResourceTranscoder
         *resourceClass是GifBitmapWrapper.class,  transcodedClass是GlideDrawable.class。
         *transcoder是GifBitmapWrapperDrawableTranscoder实例对象
         */
        if (transcoder == null) {   
            transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
        }
        //从Glide中获取DataLoaderProvider,  实际获取为ImageVideoDataLoadProvider
        DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class,
                resourceClass);
        //创建新的ModelLoader
        ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader,
                fileDescriptorModelLoader);
        //将modelLoader, transcoder和dataLoadProvider封装到FixedLoadProvider
        return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(modelLoader, transcoder, dataLoadProvider);
    }

//buildProvider()方法的执行逻辑已经分析完了,下面我们来看一下DrawableTypeRequest父类到底会执行哪些操作,

GenericRequestBuilder(Context context, Class<ModelType> modelClass,
            LoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider,
            Class<TranscodeType> transcodeClass, Glide glide, RequestTracker requestTracker, Lifecycle lifecycle) {
        this.context = context;
        //modelClass是String.class
        this.modelClass = modelClass;
        //transcodeClass是GlideDrawable.clss
        this.transcodeClass = transcodeClass;
        this.glide = glide;
        //将buildProvider()创建的FixedLoadProvider封装到ChildLoadProvider ????多此一举~_~
        this.loadProvider = loadProvider != null
                ? new ChildLoadProvider<ModelType, DataType, ResourceType, TranscodeType>(loadProvider) : null;
    }

DrawableTypeRequest的父类是GenericRequestBuilder, 其主要功能封装一些初始化参数,并将buildProvider()创建的DataLoadProvider封装到ChildLoadProvider 中。GenericRequestBuilder主要作用是通过初始化好参数去构建GenericRequest。

GenericRequestBuilder.into()

首先我们来Review一下GenericRequestBuilder.into()方法的源码, 其源码如下:

public <Y extends Target<TranscodeType>> Y into(Y target) {
        。。。。。。
        //创建Request, 实际是GenericRequest。
        Request request = buildRequest(target);
        //将Reqesut添加到lifecycle, Glide之所以能够暂停和恢复加载图片,就是在于
        //图片加载请求被添加到生命周期回调中
        lifecycle.addListener(target);
        //执行图片加载请求
        requestTracker.runRequest(request);
        return target;
    }

GenericRequestBuilder.into()代码逻辑相对简单,就是创建Reqeust交个requestTracker执行Reqeust。requestTracker.runRequest()方法的逻辑相对简单,限于篇幅和内容的要求,这里不做分析。下面我们来看一下Target是如何创建的,举我们最常使用的GenericRequestBuilder.into(ImageView)为例。Glide源码内有一个Target工厂类,里面封装了各种T创建Target的方法。 代码如下:

public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        if (GlideDrawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new GlideDrawableImageViewTarget(view);
        } else if (Bitmap.class.equals(clazz)) {
            return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new DrawableImageViewTarget(view);
        } 
        。。。。。。
    }

关于GenericRequestBuilder的内容我们先分析到这里。下面我们分析一下Request的创建源码和和执行。

GenericRequest.onSizeReady()

上面我们分析到requestTracker会执行Request,实际是执行GenericRequest的begin()方法。GenericRequest.begin()方法会调用到onSizeReady()方法,其源码如下:

public void onSizeReady(int width, int height) {
        。。。。。。
        //modelLoader 是ImageVideoModelLoader
        ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
        //dataFetcher是ImageVideoFetcher
        final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
        //transcoder是GifBitmapWrapperDrawableTranscoder 
        ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
        //执行Engine.load()方法
        loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
                priority, isMemoryCacheable, diskCacheStrategy, this);
       
    }

GenericRequest.onSizeReady()作用是获取前面创建的DataFetcher、ModelLoader和Transcoder交给Engine去执行。

Engine.load()

Engine.load()方法的源码如下:

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        。。。。。。
        //创建DecodeJob       
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
         //创建EngineRunnable实例对象
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        //执行EngineRunnable任务
        engineJob.start(runnable);
        。。。。。。
    }

Engine.load()中会创建DecodeJob,然后封装到EngineRunnable任务中去去执行。限于篇幅和内容Engine.load()方法里的其他内容暂不分析。EngineRunnable的run()发方法源码如下:

public void run() {
      。。。。。。 
      //获取解析资源的结果GlideBitmapDrawableResource, decode()方法会调用
      //到DecodeJob的decodeFromSource()方法。
      resource = decode();
      //回调给需要加载的资源组件 
     onLoadComplete(resource);
     。。。。。。
    }

run() 方法的逻辑比较简单,就是请求解析资源,然后解析结果回调给需要加载资源的组件。下面我们来看一下DecodeJob是如何解析资源的。

DecodeJob

上面我们了解到EngineRunnable的decode()方法会调用到DecodeJob的decodeFromSource(), 其源码如下:

public Resource<Z> decodeFromSource() throws Exception {
        //解析请求资源资源
        Resource<T> decoded = decodeSource();
        //将解析好的资源进行尺寸剪裁等,重新封装
        return transformEncodeAndTranscode(decoded);
    }

decodeFromSource()方法的逻辑就是解析请求的资源,把解析的结果进行二次处理(图片剪裁等)。下面我们继续分析decodeSource()方法,decodeSource()作用是获取文件流然后将文件流解析成图片资源。其源码如下:

private Resource<T> decodeSource() throws Exception {
        Resource<T> decoded = null;
        try {
            //A类型为ImageVideoWrapper, ImageVideoWrapper里封装了InputStream和FileDescriptor两种数据流自愿
            final A data = fetcher.loadData(priority);
            //将数据流解析
            decoded = decodeFromSourceData(data);
        } finally {
            fetcher.cleanup();
        }
        return decoded;
    }

//第一节的内容我们了解到DataFetcher的作用是获取文件流,这里fetcher是ImageVideoFetcher,其核心方法是loadData(), loadData()返回的数据流是ImageVideoWrapper, ImageVideoWrapper里封装了InputStream和FileDescriptor两种数据流。具体封装代码参考ImageVideoFetcher类。获取完文件流后会调用decodeFromSourceData()方法继续解析资源。decodeFromSourceData()源码如下:

private Resource<T> decodeFromSourceData(A data) throws IOException {
        。。。。。。
        //调用Decoder解析数据流,返回解析结果。A是ImageVideoWrapper, Decoder是ImageVideoBitmapDecoder
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
        。。。。。。  
        return decoded;
    }

decodeFromSourceData()作用是调用Decoder解析数据流,输入的数据流是ImageVideoWrapper, Decoder是ImageVideoBitmapDecoder。ImageVideoBitmapDecoder会根据输入流是url还是文件描述符来决定是否调用StreamBitmapDecoder还是FileDescriptorBitmapDecoder。我们就以StreamBitmapDecoder来分析一下是如何解析数据流的。其源码如下:

public Resource<Bitmap> decode(InputStream source, int width, int height) {
        //通过downsampler,下载数据解析成Bitmap
        Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
         //将Bitmap封装到Resource<Z>中
        return BitmapResource.obtain(bitmap, bitmapPool);
    }

StreamBitmapDecoder.decode()作用就是通过Downsampler下载文件流,解析成Bitmap,让后将Bitmap封装到Resource<Z>中。Downsampler.decode()会调用到Downsampler.decodeStream(),其源码如下:

private static Bitmap decodeStream(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream,
            BitmapFactory.Options options) {
        。。。。。。
        //通过BitmapFactory解析文件流获取Bitmap
        final Bitmap result = BitmapFactory.decodeStream(is, null, options);
        return result;
    }

decodeStream()作用就是将InputStream交给BitmapFactory获取Bitmp。 输入解析成Bitmap分析完毕。
回到前面decodeFromSource()方法,在数据解析完后,会继续调用transformEncodeAndTranscode()将ResourceBitmap重新处理封装。transformEncodeAndTranscode()会调用transcoder的transcode()方法。其源码如下:

    public Resource<GlideBitmapDrawable> transcode(Resource<Bitmap> toTranscode) {
        //创建GlideBitmapDrawable实例对象,从toTranscode获取Bitmap封装到GlideBitmapDrawable, 
        //本质是个Drawable对象。
        GlideBitmapDrawable drawable = new GlideBitmapDrawable(resources, toTranscode.get());
        //将GlideBitmapDrawable封装到GlideBitmapDrawableResource中。
        return new GlideBitmapDrawableResource(drawable, bitmapPool);
    }

transcoder作用是将Bitmap转化为Drawale类型资源。
继续回到EngineRunnable对象的run()方法中,解析好的GlideBitmapDrawableResource会被onLoadComplete()方法回调。最终回调到GenericRequest的onResourceReady()方法中,其源码如下:

public void onResourceReady(Resource<?> resource) {
        //resource是GlideBitmapDrawableResource, Object是GlideBitmapDrawable
        Object received = resource.get();
         //回调onResourceReady()方法
        onResourceReady(resource, (R) received);
    }

GenericRequest的onResourceReady()逻辑就是从GlideBitmapDrawableResource中获取GlideBitmapDrawable,然后回调onResourceReady(Resource<?> resource, R result)方法, 源码如下:

private void onResourceReady(Resource<?> resource, R result) {
        //将资源result设置到target中。
        target.onResourceReady(result, animation);
    }

其作用是将将资源result设置到target中。以ImageView的Target目标为例,我们来分析一下onResourceReady()源码,源码如下:

public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
        。。。。。。
        //调用父类的onResourceReady()方法
        super.onResourceReady(resource, animation);
        this.resource = resource;
    }

其父类的的onResourceReady()回调用到的 setResource()方法,其源码如下:

protected void setResource(GlideDrawable resource) {
        //将Drawable对象设置到view中
        view.setImageDrawable(resource);
 }

setResource()将GlideDrawable对象设置到View。支持整个源码流程分析完毕。

总结

相对于其他第三方图片加载开源框架,Glide有点在于其拥有生命周期管理。代码框架设计抽象、内部耦合性小拓展方便,业务组件和通过注册的方式去支持实现。但是Glide的框架实现过于复杂,泛型使用广泛,构造函数过多,看着着实眼花缭乱,不利于短时间内消化。正应对那句话简洁不简单。 对于开源项目的初学者来说,Glide并不是一个好的项目,门槛太高,本篇分析希望成为源码分析者的一个参考,希望大家提出宝贵意见,继续优化Glide分析文档。

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

推荐阅读更多精彩内容