SDwebImage YYImage lottie 加載gif 動畫,性能比較校套,gif加載框架選型分析

最近項(xiàng)目改版价脾,要加載好幾個(gè)gif的動畫,剛開始嘗試使用了SDwebImage來加載顯示笛匙,可以正常顯示侨把,但加載多個(gè)gif的時(shí)候會被閃退,查找原因是內(nèi)存爆增引起的妹孙。
后來有嘗試使用YYkit 框架秋柄,來加載gif 動畫,YYKit 加載gif 的時(shí)候不會引起內(nèi)存問題涕蜂,但是CPU的使用率會很高华匾,
最后我使用的是lottie 動畫,加載會比較友善机隙,不會存在內(nèi)存和CPU的問題蜘拉,
接下來我們依次來分析一下相關(guān)的代碼

1、SDwebImage

@property (nonatomic, strong) UIImageView  *gifImageview;

self.gifImageview = [[SDAnimatedImageView alloc]init];
    [self.view addSubview:self.gifImageview];
[self.gifImageview mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.mas_equalTo(self.view);
        make.centerY.mas_equalTo(self.view);
        make.height.equalTo(@(200));
    }];

 // 這里我使用的是本地的文件有鹿,本gif 內(nèi)存是1.3M 包含60張圖片
NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"轉(zhuǎn)場_1"] ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:path];
UIImage *image = [UIImage sd_imageWithGIFData:data];    [self.gifImageview setImage:image];

加載出來通過Xcode 查看旭旭,發(fā)現(xiàn)Memory 會很高,如下圖葱跋,這個(gè)是單獨(dú)一個(gè)gif持寄,我如果加載3個(gè)gif源梭,Memory會爆增到2G


Snip20200820_12.png

1、我們通過sdwebImage 的源碼分析一下問題的存在
sd_imageWithGIFData是SDwebImage 的 UIImage+GIF中方法稍味,里面直接調(diào)用 [[SDImageGIFCoder sharedCoder] decodedImageWithData:data options:0];废麻,接下來我們直接看一下decodedImageWithData 的源碼

#import "SDImageIOAnimatedCoder.h"
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
//安全判斷
    if (!data) {
        return nil;
    }
    CGFloat scale = 1;
    //option 為 nil ,scalefactor  thumbnailSizeValue 暫時(shí)無用
    NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
    if (scaleFactor != nil) {
        scale = MAX([scaleFactor doubleValue], 1);
    }
    
    CGSize thumbnailSize = CGSizeZero;
    //解碼縮略圖像素大小
    NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
    if (thumbnailSizeValue != nil) {
#if SD_MAC
        thumbnailSize = thumbnailSizeValue.sizeValue;
#else
        thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
    }
    
    BOOL preserveAspectRatio = YES;
    NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
    if (preserveAspectRatioValue != nil) {
        preserveAspectRatio = preserveAspectRatioValue.boolValue;
    }
    
#if SD_MAC
    // If don't use thumbnail, prefers the built-in generation of frames (GIF/APNG)
    // Which decode frames in time and reduce memory usage
    if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
        SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
        NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
        imageRep.size = size;
        NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
        [animatedImage addRepresentation:imageRep];
        return animatedImage;
    }
#endif
    //二進(jìn)制類型的轉(zhuǎn)換
    //CGImageSourceRef是個(gè)什么呢? 我們可以看到這是一個(gè)typedef CGImageSource * CGImageSourceRef;
    //這是一個(gè)指針,CGImageSource是對圖像數(shù)據(jù)讀取任務(wù)的抽象模庐,通過它可以獲得圖像對象烛愧、縮略圖、圖像的屬性(包括Exif信息)掂碱。
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    if (!source) {
        return nil;
    }
    //獲取有幾張圖片
    size_t count = CGImageSourceGetCount(source);
    //返回的動態(tài)圖片
    UIImage *animatedImage;
    
    BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
    if (decodeFirstFrame || count <= 1) {
        animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
    } else {
    //集合 存放單張的圖片
        NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
        
        for (size_t i = 0; i < count; i++) {
//            獲取gif每一幀圖像
            UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
            if (!image) {
                continue;
            }
            // 獲取每一幀圖像對應(yīng)的顯示時(shí)間
            NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
            //創(chuàng)建動圖
            SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
            [frames addObject:frame];
//            作者獲取每一幀圖像的顯示時(shí)間的目的僅僅是為了計(jì)算gif動畫的總時(shí)長怜姿,并沒有給每一幀圖像的顯示時(shí)間分配相應(yīng)的權(quán)重,導(dǎo)致每一幀圖像顯示的時(shí)間為平均時(shí)間
        }
        
        NSUInteger loopCount = [self.class imageLoopCountWithSource:source];
        NSLog(@"%lu",(unsigned long)loopCount);
        //把靜態(tài)的圖片轉(zhuǎn)換為動態(tài)的image,所以會有大量單張的圖片存放在內(nèi)存中
        animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
        animatedImage.sd_imageLoopCount = loopCount;
    }
    animatedImage.sd_imageFormat = self.class.imageFormat;
    //釋放圖像數(shù)據(jù)讀取任務(wù)的抽象對象
    CFRelease(source);
    
    return animatedImage;
}

發(fā)現(xiàn)SDWebImage處理gif圖片的方法是:將gif資源中每一張imgae寫入到內(nèi)存中疼燥,通過animatedImageWithImages的方式播放動畫沧卢。這樣的好處是,gif輪詢播放時(shí)醉者,直接從內(nèi)存中取資源就好了但狭,降低了cpu的占用。也就是說撬即,SDWebImage是以空間換取的流暢度熟空。

2 YYKIt

我們來創(chuàng)建 imageView ,展示我們的內(nèi)容

@property (nonatomic,strong) YYAnimatedImageView *YYImageView;

self.YYImageView = [[YYAnimatedImageView alloc]init];
[self.view addSubview:self.YYImageView];

[self.YYImageView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.left.right.mas_equalTo(self.view);
     make.centerY.mas_equalTo(self.view);
     make.height.equalTo(@(200));
 }];
 
 YYImage *yyimage = [YYImage imageNamed:@"轉(zhuǎn)場_1"];
 [self.YYImageView setImage:yyimage];

YYkit 加載gif 的過程中搞莺,memory 基本不會過度使用息罗,但是最初會有一段時(shí)間CPU的使用率會很高達(dá)到 98 %,


Snip20200820_13.png

    if (!_finalized && index > 0) return NULL;
    if (_frames.count <= index) return NULL;
    _YYImageDecoderFrame *frame = _frames[index];
    
    if (_source) {
        CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_source, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)});
        if (imageRef && extendToCanvas) {
            size_t width = CGImageGetWidth(imageRef);
            size_t height = CGImageGetHeight(imageRef);
            if (width == _width && height == _height) {
           // 每次的展示都會調(diào)用create 方法 
                CGImageRef imageRefExtended = YYCGImageCreateDecodedCopy(imageRef, YES);
                if (imageRefExtended) {
                    CFRelease(imageRef);
                    imageRef = imageRefExtended;
                    if (decoded) *decoded = YES;
                }
            } else {
                CGContextRef context = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
                if (context) {
                    CGContextDrawImage(context, CGRectMake(0, _height - height, width, height), imageRef);
                    CGImageRef imageRefExtended = CGBitmapContextCreateImage(context);
                    CFRelease(context);
                    if (imageRefExtended) {
                        CFRelease(imageRef);
                        imageRef = imageRefExtended;
                        if (decoded) *decoded = YES;
                    }
                }
            }
        }
        return imageRef;
    }
    

發(fā)現(xiàn)了YYKit處理gif圖片的方法是:每次從緩存的gif中才沧,讀取當(dāng)前需要展示的image迈喉,進(jìn)行動畫展示。這樣做的好處是温圆,不用為gif的每張image開辟空間了挨摸,每次都是從一份gif資源中讀取一張image就好了。以一定的幀率從緩沖中解析出當(dāng)前需要展示的image岁歉,肯定是需要耗用cpu的得运。

3、lottie

我們先來創(chuàng)建一個(gè)lottie锅移,lottie 加載gif 動畫需要把動畫轉(zhuǎn)換成json數(shù)據(jù)熔掺,

@property (nonatomic, strong) LOTAnimationView  *loImageView;

self.loImageView = [LOTAnimationView animationNamed:@"1"];
[self.loImageView play];
self.loImageView.loopAnimation = YES;
[self.view addSubview:self.loImageView];
[self.loImageView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.left.right.mas_equalTo(self.view);
     make.centerY.mas_equalTo(self.view);
     make.height.equalTo(@(200));
}];

 self.loImageView2 = [LOTAnimationView animationNamed:@"2"];
 [self.loImageView2 play];
 [self.view addSubview:self.loImageView2];

通過lottie 加載同樣的gif動畫看到的CPU 和memory 的數(shù)據(jù)


Snip20200820_16.png
3-1 接下來我們看一下lottie 的原理,具體使用方法請參考()iOS Lottie動畫接入過程詳解

1非剃、Lottie是Airbnb開源的一個(gè)動畫渲染庫置逻,支持多平臺,包括iOS备绽、Android券坞、React Native以及Flutter(https://github.com/airbnb/lottie-ios)鬓催。除了官方支持的平臺,更有大神實(shí)現(xiàn)了支持Windows恨锚、Qt宇驾、Skia以及React、Vue猴伶、Angular等平臺
Lottie動畫產(chǎn)生的流程如下:

image

注意點(diǎn):Lottie 3.0之后已經(jīng)全部使用swift實(shí)現(xiàn)飞苇,所以如果需要使用Objective-C版本需要使用Lottie 2.5.3版本

3-2 Lottie 原理

1、需要我們的設(shè)計(jì)把gif 動畫專程json
例如:


Snip20200820_17.png

Lottie整體的原理如下:

1)首先要知道蜗顽,一個(gè)完整動畫View,是由很多個(gè)子Layer 組成雨让,而每個(gè)子Layer主要通過shapes(形狀)雇盖,masks(蒙版),transform三大部分進(jìn)行動畫栖忠。
2)Lottie框架通過讀取JSON文件崔挖,獲取到每個(gè)子Layer 的shapes,masks庵寞,以及出現(xiàn)時(shí)間狸相,消失時(shí)間以及Transform各個(gè)屬性的關(guān)鍵幀數(shù)組。
3)動畫則是通過給CompositionLayer (所有的子layer都添加在這個(gè)Layer 上)的 currentFrame屬性添加一個(gè)CABaseAnimation 來實(shí)現(xiàn)捐川。
4)所有的子Layer根據(jù)currentFrame 屬性的變化脓鹃,根據(jù)JSON中的關(guān)鍵幀數(shù)組計(jì)算出自己的當(dāng)前狀態(tài)并進(jìn)行顯示。

接下來讓我們深入它的源碼(OC版本)去看看古沥,對它的原理有一個(gè)更深刻的認(rèn)識

1)入口類為LOTAnimationView瘸右,提供了一系列加載和設(shè)置動畫的方法及屬性以及對動畫的操作,這里列舉一二

+ (nonnull instancetype)animationNamed:(nonnull NSString *)animationName NS_SWIFT_NAME(init(name:));
+ (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON NS_SWIFT_NAME(init(json:));
+ (nonnull instancetype)animationFromJSON:(nullable NSDictionary *)animationJSON 
inBundle:(nullable NSBundle *)bundle NS_SWIFT_NAME(init(json:bundle:));
...
@property (nonatomic, assign) CGFloat animationProgress;
@property (nonatomic, assign) CGFloat animationSpeed;
...
- (void)play;
- (void)pause; 

LOTAnimationView所有的加載方法岩齿,最終執(zhí)行的都是把JSON字典傳到LOTComposition類中太颤,組裝LOTComposition對象,當(dāng)然還會有一些緩存獲取盹沈,值判斷等的邏輯龄章,但是核心就是產(chǎn)生一個(gè)LOTComposition對象:

+ (nullable instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle {
  ...
  if (JSONObject && !error) {
    LOTComposition *laScene = [[self alloc] initWithJSON:JSONObject withAssetBundle:bundle];
    [[LOTAnimationCache sharedCache] addAnimation:laScene forKey:animationName];
    laScene.cacheKey = animationName;
    return laScene;
  }
  NSLog(@"%s: Animation Not Found", __PRETTY_FUNCTION__);
  return nil;
}

2)LOTComposition類用來解析整個(gè)動畫的json字典,獲取整個(gè)動畫所需的數(shù)據(jù)乞封。

- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
     withAssetBundle:(NSBundle *)bundle {
  NSNumber *width = jsonDictionary[@"w"];
  NSNumber *height = jsonDictionary[@"h"];
  if (width && height) {
    CGRect bounds = CGRectMake(0, 0, width.floatValue, height.floatValue);
    _compBounds = bounds;
  }
  
  _startFrame = [jsonDictionary[@"ip"] copy];
  _endFrame = [jsonDictionary[@"op"] copy];
  _framerate = [jsonDictionary[@"fr"] copy];
  
  if (_startFrame && _endFrame && _framerate) {
    NSInteger frameDuration = (_endFrame.integerValue - _startFrame.integerValue) - 1;
    NSTimeInterval timeDuration = frameDuration / _framerate.floatValue;
    _timeDuration = timeDuration;
  }
  
  NSArray *assetArray = jsonDictionary[@"assets"];
  if (assetArray.count) {
    _assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle withFramerate:_framerate];
  }
  
  NSArray *layersJSON = jsonDictionary[@"layers"];
  if (layersJSON) {
    _layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
                                            withAssetGroup:_assetGroup
                                             withFramerate:_framerate];
  }
  
  [_assetGroup finalizeInitializationWithFramerate:_framerate];
}

在對JSON字典的解析過程中做裙,會拆分成幾種不同的信息,包括:整體關(guān)鍵幀信息肃晚、所需圖片資源信息菇用、所有子layer的信息。并將圖片組和layer組分別傳入到LOTAssetGroup和LOTLayerGroup中做進(jìn)一步處理陷揪。

3)LOTLayerGroup類用于解析JSON中“l(fā)ayers”層的數(shù)據(jù)惋鸥,并將單獨(dú)的layer數(shù)據(jù)傳遞給LOTLayer處理杂穷。核心代碼如下:

- (void)_mapFromJSON:(NSArray *)layersJSON
      withAssetGroup:(LOTAssetGroup * _Nullable)assetGroup
       withFramerate:(NSNumber *)framerate {
  
  NSMutableArray *layers = [NSMutableArray array];
  NSMutableDictionary *modelMap = [NSMutableDictionary dictionary];
  NSMutableDictionary *referenceMap = [NSMutableDictionary dictionary];
  
  for (NSDictionary *layerJSON in layersJSON) {
    LOTLayer *layer = [[LOTLayer alloc] initWithJSON:layerJSON
                                      withAssetGroup:assetGroup
                                       withFramerate:framerate];
    [layers addObject:layer];
    modelMap[layer.layerID] = layer;
    if (layer.referenceID) {
      referenceMap[layer.referenceID] = layer;
    }
  }
  
  _referenceIDMap = referenceMap;
  _modelMap = modelMap;
  _layers = layers;
}

4)接下來進(jìn)入LOTLayer類中,這里最終把json文件中單個(gè)layer對應(yīng)的數(shù)據(jù)映射出來卦绣。

@property (nonatomic, readonly) NSString *layerName;
@property (nonatomic, readonly, nullable) NSString *referenceID;
@property (nonatomic, readonly) NSNumber *layerID;
@property (nonatomic, readonly) LOTLayerType layerType;
@property (nonatomic, readonly, nullable) NSNumber *parentID;
@property (nonatomic, readonly) NSNumber *startFrame;
@property (nonatomic, readonly) NSNumber *inFrame;
@property (nonatomic, readonly) NSNumber *outFrame;
@property (nonatomic, readonly) NSNumber *timeStretch;
@property (nonatomic, readonly) CGRect layerBounds;
@property (nonatomic, readonly, nullable) NSArray<LOTShapeGroup *> *shapes;
@property (nonatomic, readonly, nullable) NSArray<LOTMask *> *masks;

以上屬性和json文件對應(yīng)的key有一一對應(yīng)關(guān)系耐量,比如layerName對應(yīng)json文件中的nm,layerType對應(yīng)ty等等滤港,每個(gè)layer中包含Layer所需的基本信息廊蜒,transform變化需要的則是每個(gè)LOTKeyframeGroup 類型的屬性。這里面包含了該Layer 的 transform變化的關(guān)鍵幀數(shù)組溅漾,而masks 和 shapes 的信息包含在上面的兩個(gè)同名數(shù)組中山叮。

5)前面四步,已經(jīng)把動畫需要的數(shù)據(jù)全部準(zhǔn)備好了添履,接下來就需要進(jìn)行動畫顯示屁倔。
最底層的LOTLayerContainer繼承自CALayer,添加了currentFrame屬性暮胧,LOTCompositionContainer又是繼承自LOTLayerContainer锐借,為LOTCompositionContainer對象添加了一個(gè)CABaseAnimation動畫,然后重寫CALayer的display方法往衷,在display方法中通過 CALayer中的presentationLayer獲取在動畫中變化的currentFrame數(shù)值 钞翔,再通過遍歷每一個(gè)子layer,將更新后的currentFrame傳入席舍,來實(shí)時(shí)更新每一個(gè)子Layer的顯示布轿。核心代碼在LOTLayerContainer中,如下:

- (void)displayWithFrame:(NSNumber *)frame forceUpdate:(BOOL)forceUpdate {
  NSNumber *newFrame = @(frame.floatValue / self.timeStretchFactor.floatValue);
  if (ENABLE_DEBUG_LOGGING) NSLog(@"View %@ Displaying Frame %@, with local time %@", self, frame, newFrame);
  BOOL hidden = NO;
  if (_inFrame && _outFrame) {
    hidden = (frame.floatValue < _inFrame.floatValue ||
              frame.floatValue > _outFrame.floatValue);
  }
  self.hidden = hidden;
  if (hidden) {
    return;
  }
  if (_opacityInterpolator && [_opacityInterpolator hasUpdateForFrame:newFrame]) {
    self.opacity = [_opacityInterpolator floatValueForFrame:newFrame];
  }
  if (_transformInterpolator && [_transformInterpolator hasUpdateForFrame:newFrame]) {
    _wrapperLayer.transform = [_transformInterpolator transformForFrame:newFrame];
  }
  [_contentsGroup updateWithFrame:newFrame withModifierBlock:nil forceLocalUpdate:forceUpdate];
  _maskLayer.currentFrame = newFrame;
}

它實(shí)際上完成了以下幾件事:
1.根據(jù)子Layer的起始幀和結(jié)束幀判斷當(dāng)前幀子Layer是否顯示
2.更新子Layer當(dāng)前幀的透明度
3.更新子Layer當(dāng)前幀的transform
4.更新子Layer中路徑和形狀等內(nèi)容的變化

6)上面動畫顯示的2来颤,3驮捍,4步都是通過XXInterpolator這些類,來從當(dāng)前frame中計(jì)算出我們需要的值脚曾,我們以LOTTransformInterpolator為例东且,其他類似,看看它都有些什么:

@property (nonatomic, readonly) LOTPointInterpolator *positionInterpolator;
@property (nonatomic, readonly) LOTPointInterpolator *anchorInterpolator;
@property (nonatomic, readonly) LOTSizeInterpolator *scaleInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *rotationInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *positionXInterpolator;
@property (nonatomic, readonly) LOTNumberInterpolator *positionYInterpolator;

針對transform變換需要很多的信息本讥,LOTTransformInterpolator中提供了這些所需的信息珊泳。

當(dāng)傳入當(dāng)前frame時(shí),這些interpolator會返回不同的數(shù)值拷沸,從而組成當(dāng)前的transform色查。這些不同的Interpolar會根據(jù)自己的算法返回當(dāng)前所需要的值,但是他們大體的流程都是一樣的:

1.在關(guān)鍵幀數(shù)組中找到當(dāng)前frame的前一個(gè)關(guān)鍵幀(leadingKeyframe)和后一個(gè)關(guān)鍵幀(trailingKeyframe)
2.計(jì)算當(dāng)前frame 在 leadingKeyframe 和 trailingKeyframe 的進(jìn)度(progress)
3.根據(jù)這個(gè)progress以及 leadingKeyframe撞芍,trailingKeyframe算出當(dāng)前frame下的值秧了。(不同的Interpolator算法不同)

總結(jié):
Lottie提供了多種便利的方式,供我們加載酷炫的動畫序无,對用戶體驗(yàn)有極大的提升验毡。對使用者來說衡创,只需要引入包含動效的json文件和資源文件,調(diào)用lottie提供的屬性和api完成動畫繪制晶通。Lottie內(nèi)部幫我們做了json文件映射到不同類的不同屬性中璃氢,通過一系列的計(jì)算,確定出每一幀的數(shù)據(jù)狮辽,然后完美的顯示在屏幕上一也,這樣的神器,以后要多多用起來啦喉脖!


參考文章:Lottie動畫使用及原理分析
YYText 源碼剖析:CoreText 與異步繪制
YYImage 設(shè)計(jì)思路椰苟,實(shí)現(xiàn)細(xì)節(jié)剖析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市树叽,隨后出現(xiàn)的幾起案子舆蝴,更是在濱河造成了極大的恐慌,老刑警劉巖菱皆,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異挨稿,居然都是意外死亡仇轻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門奶甘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篷店,“玉大人,你說我怎么就攤上這事臭家∑I拢” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵钉赁,是天一觀的道長蹄殃。 經(jīng)常有香客問我,道長你踩,這世上最難降的妖魔是什么诅岩? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮带膜,結(jié)果婚禮上吩谦,老公的妹妹穿的比我還像新娘。我一直安慰自己膝藕,他們只是感情好式廷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芭挽,像睡著了一般滑废。 火紅的嫁衣襯著肌膚如雪蝗肪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天策严,我揣著相機(jī)與錄音穗慕,去河邊找鬼。 笑死妻导,一個(gè)胖子當(dāng)著我的面吹牛逛绵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倔韭,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼术浪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寿酌?” 一聲冷哼從身側(cè)響起胰苏,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎醇疼,沒想到半個(gè)月后硕并,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秧荆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年倔毙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乙濒。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陕赃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出颁股,到底是詐尸還是另有隱情么库,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布甘有,位于F島的核電站诉儒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏亏掀。R本人自食惡果不足惜允睹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幌氮。 院中可真熱鬧缭受,春花似錦、人聲如沸该互。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蔓搞,卻和暖如春胰丁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喂分。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工锦庸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蒲祈。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓甘萧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梆掸。 傳聞我的和親對象是個(gè)殘疾皇子扬卷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345