SDWebImage原理解析

SDWebImage :github托管地址https://github.com/rs/SDWebImage

一刑枝、功能簡介:

  1. 一個添加了web圖片加載和緩存管理的UIImageView分類

  2. 一個異步圖片下載器

  3. 一個異步的內(nèi)存加磁盤綜合存儲圖片并且自動處理過期圖片

  4. 支持動態(tài)gif圖

  5. 支持webP格式的圖片

  6. 后臺圖片解壓處理

  7. 確保同樣的圖片url不會下載多次

  8. 確保偽造的圖片url不會重復(fù)嘗試下載

  9. 確保主線程不會阻塞

二豺憔、SDWebImage 加載圖片的流程

  1. 入口 setImageWithURL:placeholderImage:options:會先把 placeholderImage 顯示,然后 SDWebImageManager 根據(jù) URL 開始處理圖片

  2. 進(jìn)入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:霜浴,交給 SDImageCache 從緩存查找圖片是否已經(jīng)下載 queryDiskCacheForKey:delegate:userInfo:

    1. 先從內(nèi)存圖片緩存查找沮趣,如果內(nèi)存中已經(jīng)有圖片緩存,SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo:SDWebImageManager
    2. SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片
  3. 如果內(nèi)存緩存中沒有坷随,生成 NSInvocationOperation 添加到隊(duì)列開始從硬盤查找圖片是否已經(jīng)緩存

    1. 根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件房铭。這一步是在 NSOperation 進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào) notifyDelegate:
    2. 如果上一操作從硬盤讀取到了圖片温眉,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小缸匪,會先清空內(nèi)存緩存)。SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo:类溢;進(jìn)而回調(diào)展示圖片
  4. 如果從硬盤緩存目錄讀取不到圖片凌蔬,說明所有緩存都不存在該圖片,需要下載圖片闯冷,回調(diào) imageCache:didNotFindImageForKey:userInfo:

  5. 共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片

  6. connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理

    1. 圖片解碼處理在一個 NSOperationQueue 完成砂心,不會拖慢主線程 UI。如果有需要對下載的圖片進(jìn)行二次處理蛇耀,可以在這里完成辩诞,效率會好很多。
    2. 在主線程 notifyDelegateOnMainThreadWithInfo:宣告解碼完成纺涤,imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給 SDWebImageDownloader
  7. imageDownloader:didFinishWithImage: 回調(diào)給 SDWebImageManager 告知圖片下載完成

  8. 通知所有的 downloadDelegates 下載完成译暂,回調(diào)給需要的地方展示圖片

  9. 將圖片保存到 SDImageCache 中,內(nèi)存緩存和硬盤緩存同時保存撩炊。寫文件到硬盤也在以單獨(dú) NSInvocationOperation 完成外永,避免拖慢主線程

三、圖片緩存策略: (不緩存拧咳,內(nèi)存緩存伯顶,沙盒緩存)

  1. SDImageCache是怎么做數(shù)據(jù)管理的?

    1. SDImageCache分兩個部分,一個是內(nèi)存層面的祭衩,一個是硬盤層面的灶体。

    2. 內(nèi)存層面的相當(dāng)是個緩存器,以Key-Value的形式存儲圖片汪厨。當(dāng)內(nèi)存不夠的時候會清除所有緩存圖片。

    3. 用搜索文件系統(tǒng)的方式做管理愉择,文件替換方式是以時間為單位劫乱,剔除時間大于一周的圖片文件

    4. 當(dāng)SDWebImageManager向SDImageCache要資源時,先搜索內(nèi)存層面的數(shù)據(jù)锥涕,如果有直接返回衷戈,沒有的話去訪問磁盤,將圖片從磁盤讀取出來层坠,然后做Decoder殖妇,將圖片對象放到內(nèi)存層面做備份,再返回調(diào)用層破花。

    5. 具體代碼:

      1. Memory Cache:
      @interface SDImageCache ()
      #pragma mark - Properties
      @property (strong, nonatomic, nonnull) NSCache *memCache;
      // 這里我們發(fā)現(xiàn)谦趣, 有一個叫做 memCache 的屬性,它是一個 NSCache 對象座每,用于實(shí)現(xiàn)我們對圖片的 Memory Cache前鹅。
      // NSCache 簡單來說,它是一個類似于 NSDictionary 的集合類峭梳,用于在內(nèi)存中存儲我們要緩存的數(shù)據(jù)舰绘。詳細(xì)信息大家可以參考官方文檔:https://developer.apple.com/reference/foundation/nscache
      
      // SDWebImage 還專門實(shí)現(xiàn)了一個叫做 AutoPurgeCache 的類, 相比于普通的 NSCache葱椭, 它提供了一個在內(nèi)存緊張時候釋放緩存的能力:
      // 接受系統(tǒng)的內(nèi)存警告通知捂寿,然后清除掉自身的圖片緩存
      @interface AutoPurgeCache : NSCache
      @end
      @implementation AutoPurgeCache
      - (nonnull instancetype)init {
          self = [super init];
          if (self) {
      #if SD_UIKIT
              [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
      #endif
          }
          return self;
      }
      
      1. Disk Cache:

      也就是文件緩存,SDWebImage 會將圖片存放到 NSCachesDirectory 目錄中孵运,然后為每一個緩存文件生成一個 md5 文件名, 存放到文件中秦陋。

  1. Disk 緩存清理策略

    1. SDWebImage 會在每次 APP 結(jié)束和程序切到后臺的時候執(zhí)行清理任務(wù)。 清理緩存的規(guī)則分兩步進(jìn)行治笨。 第一步先清除掉過期的緩存文件踱侣。 如果清除掉過期的緩存之后,空間還不夠大磺。 那么就繼續(xù)按文件時間從早到晚排序抡句,先清除最早的緩存文件,直到剩余空間達(dá)到要求杠愧。

    2. 具體點(diǎn)待榔,SDWebImage 是怎么控制哪些緩存過期,以及剩余空間多少才夠呢? 通過兩個屬性:

      @interface SDImageCache : 
      NSObject@property (assign, nonatomic) NSInteger maxCacheAge;//文件緩存的時長
      @property (assign, nonatomic) NSUInteger maxCacheSize;//允許的最大緩存空間
      
      //  maxCacheAge 的默認(rèn)值
      static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
      // maxCacheSize 的默認(rèn)值
      [SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50;  // 50M
      

四锐锣、圖片Decoder

  1. 為啥必須做Decoder?

    1. 首先來了解下幾種圖片格式的區(qū)別(png, jpg, gif ,webp,bitMap等等):

      1. jpeg :

        1. 有損壓縮格式, 將像素信息用jpeg保存成文件再讀取出來腌闯,其中某些像素值會有少許變化。
        2. 沒有透明信息
        3. jpeg比較適合用來存儲相機(jī)拍攝出來的圖片
      2. png :

        1. 是一種無損壓縮格式:因?yàn)樗鼈兪褂玫?(主要是基于游程長度編碼的) 壓縮算法可以減少存儲需求雕憔。這種壓縮是無損的姿骏,這意味著圖像質(zhì)量不會被壓縮過程影響
        2. 可以有透明效果
        3. 比較適合矢量圖,幾何圖

        (矢量格式的一大優(yōu)點(diǎn)就是縮放.矢量格式的圖像其實(shí)是一組繪圖命令.這些指令通常是獨(dú)立于尺寸的.如果你想要擴(kuò)大一個圓形,只需在繪制之前擴(kuò)大他的半徑就可以了)

      3. bitMap(位圖):

        1. bmp格式?jīng)]有壓縮像素格式
        2. 存儲在文件中時先有文件頭、再圖像頭斤彼、后面就都是像素?cái)?shù)據(jù)了分瘦,上下顛倒存儲
        3. 最早接觸到bitMap是在imageView的layer.shouldRasterize
    2. CALayer的shouldRasterize和離屏渲染(offscreen rendering):

      1. 離屏渲染消耗性能原因是顯卡需要另外alloc一塊內(nèi)存來進(jìn)行渲染,渲染完畢后在繪制到當(dāng)前屏幕,而且對于顯卡來說,onscreen到offscreen的上下文環(huán)境切換是非常昂貴的(涉及到OpenGL的pipelines和barrier等).

      2. 開啟shouldRasterize后,CALayer會被光柵化為bitmap,layer的陰影等效果也會被保存到bitmap中.

        更新已光柵化的layer,會造成大量的offscreen渲染.

      3. 當(dāng)你設(shè)置圖片的圓角屬性的時候,會觸發(fā)GPU的離屏渲染琉苇,這個過程消耗性能嘲玫。當(dāng)設(shè)置這個屬性( shouldRasterize )后為YES后 the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content. rendered as a bitmap這個過程是由CPU完成的,等下次使用時不會再重新去渲染了并扇,直接使用bitmap去团。如果不設(shè)置這個屬性為YES,the layer is composited directly into the destination whenever possible.造成離屏渲染

      參考文章Advanced Graphics and Animations for iOS Apps

    3. 圖片的在內(nèi)存中的大小

      1. 圖片在內(nèi)存中占用的大小 跟圖片自身的大小沒有關(guān)系

      2. 內(nèi)存中占用的大小 = 圖片的寬度 * 圖片的高度*每個像素占用的字節(jié)數(shù)

      3. 在SDWebImage中衡量大圖的標(biāo)準(zhǔn):圖片的像素總數(shù) > 60M內(nèi)存所存儲的像素?cái)?shù) ? 壓縮 : 不壓縮

        1. 蘋果官方的加載大圖demo

        2. 這個里面的大圖處理邏輯個SDWebImage 是完全一樣的,介紹更詳細(xì),建議看蘋果官方的

        3. 基本原理:也就是原圖按照定好的大小(像素).來對原圖進(jìn)行切塊,然后再一塊塊的繪制到destContext

        4. SDWebImage部份可以參考:+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image函數(shù)

    4. png,jpeg格式的數(shù)據(jù)是不能直接使用的,需要將其轉(zhuǎn)化為位圖

    5. 當(dāng)我們使用imageView顯示圖片的時候:

      1. 讀取圖片
      2. 解壓圖片為位圖(消耗CPU)
      3. 如果位圖數(shù)據(jù)不是字節(jié)對齊的穷蛹,CoreAnimation會copy一份位圖數(shù)據(jù)并進(jìn)行字節(jié)對齊
      4. CoreAnimation渲染解壓縮過的位圖
      5. 這一切在IOS中都是默認(rèn)發(fā)生在主線程成的并且是在UIImageView執(zhí)行setImage方法的時候完成的
    6. 補(bǔ)充點(diǎn):

      1. +(nullableUIImage*)imageNamed:(NSString*)name:不適合加載大的 不常用的圖片.因?yàn)樗鼤J(rèn)在程序里保存這張圖片數(shù)據(jù)(不會隨ImageView的移除而移除).只有經(jīng)常使用圖片適合這種方式加載.
      2. + (nullableUIImage*)imageWithContentsOfFile:(NSString*)path:這個方法跟上面的略有不同,他不會在內(nèi)存中保留一份數(shù)據(jù).只要imageView移除,內(nèi)存中的數(shù)據(jù)就會直接移除.這也就是這個方法為什么適合加載大的圖片,但卻不常用的圖片.
      3. 相關(guān)的參考文章:IOS異步圖片加載與常用的優(yōu)化
  2. 解碼部份的代碼:

    // 用來說明每個像素占用內(nèi)存多少個字節(jié)土陪,在這里是占用4個字節(jié)
    static const size_t kBytesPerPixel = 4;
    /** 表示每一個組件占多少位
     比方說RGBA,其中R(紅色)G(綠色)B(藍(lán)色)A(透明度)是4個組件肴熏,每個像素由這4個組件組成旺坠,那么我們就用8位來表示著每一個組件,所以這個RGBA就是8*4 = 32位扮超。
     */
    static const size_t kBitsPerComponent = 8;
    
    /**
     知道了kBitsPerComponent和每個像素有多少組件組成就能計(jì)算kBytesPerPixel了取刃。計(jì)算公式是:(bitsPerComponent * number of components + 7)/8.
    */
    
    + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
         /** 判斷要不要解碼
         并不是所有的image都要解碼的〕鏊ⅲ可以來看看shouldDecodeImage:這個函數(shù):
         不適合解碼的條件為:
         1. image為nil
         2. animated images 動圖不適合
         3. 帶有透明因素的圖像不適合
         */
        if (![UIImage shouldDecodeImage:image]) {
            return image;
        }
       
        // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
        // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
        @autoreleasepool{
            
            CGImageRef imageRef = image.CGImage;
             // 顏色空間
            CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
            
            size_t width = CGImageGetWidth(imageRef);
            size_t height = CGImageGetHeight(imageRef);
             // 計(jì)算出每行的像素?cái)?shù) 
            size_t bytesPerRow = kBytesPerPixel * width;
    
            // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
            // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
            // to create bitmap graphics contexts without alpha info.
             // 創(chuàng)建沒有透明因素的bitmap graphics contexts
            CGContextRef context = CGBitmapContextCreate(NULL,
                                                         width,
                                                         height,
                                                         kBitsPerComponent,
                                                         bytesPerRow,
                                                         colorspaceRef,
                                                         kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
            if (context == NULL) {
                return image;
            }
            
            // Draw the image into the context and retrieve the new bitmap image without alpha
             // 繪制圖像
            CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
            CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
            UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                             scale:image.scale
                                                       orientation:image.imageOrientation];
            
            CGContextRelease(context);
            CGImageRelease(imageRefWithoutAlpha);
            
            return imageWithoutAlpha;
        }
    }
    
  1. 總結(jié):結(jié)合上面五點(diǎn)總結(jié)下為什么要解碼:

    其實(shí)不解碼也是可以使用的璧疗,假如說我們通過imageNamed:來加載image,系統(tǒng)默認(rèn)會在主線程立即進(jìn)行圖片的解碼工作馁龟。這一過程就是把image解碼成可供控件直接使用的位圖崩侠。

    當(dāng)在主線程調(diào)用了大量的imageNamed:方法后,就會產(chǎn)生卡頓了坷檩。為了解決這個問題我們有兩種比較簡單的處理方法:

    1. 我們不使用imageNamed:加載圖片却音,使用其他的方法,比如imageWithContentsOfFile:
    2. 我們自己解碼圖片矢炼,可以把這個解碼過程放到子線程

    好處:

    1. 把圖片解碼這個默認(rèn)在主線程執(zhí)行,耗損CPU的行為,放在了后臺線程.

    2. 只需要在使用的時候,直接setImage,不會有太大的CPU消耗

    弊端:

    1. 這樣解碼就是以空間換時間的方法,提前解壓好,用的時候直接從內(nèi)存讀取.
    2. 如果下載的圖片比較大,然后直接解碼的話 這個是內(nèi)存所不能承受,需要對圖片進(jìn)行壓縮.

五系瓢、部份API說明:

  1. 所有類的作用介紹一下(3.8.1版本)

    SDImageCache  //緩存  定義了 Disk  和  memory二級緩存(NSCache)負(fù)責(zé)管理cache  單例
    SDWebImageManager  //核心管理類 主要對緩存管理 + 下載管理進(jìn)行了封裝  主要接口downloadImageWithURL單利
    SDWebImageDownloader //異步圖片下載管理,管理下載隊(duì)列句灌,管理operation 管理網(wǎng)絡(luò)請求 處理結(jié)果和異常夷陋,單例
    SDWebImageCompat  //保證不同平臺/版本/屏幕等兼容性的宏定義和內(nèi)聯(lián) 圖片縮放
    SDWebImageDecoder  //圖片解壓縮欠拾,內(nèi)部只有一個接口
    存放網(wǎng)絡(luò)請求回調(diào)的block  //自己理解的數(shù)據(jù)結(jié)構(gòu)大概是
    //  結(jié)構(gòu){"url":[{"progress":"progressBlock"},{"complete":"completeBlock"}]}
    
    SDWebImageDownloaderOperation //實(shí)現(xiàn)了異步下載圖片的NSOperation,網(wǎng)絡(luò)請求給予NSURLSession 代理下載,自定義的Operation任務(wù)對象骗绕,需要手動實(shí)現(xiàn)start cancel等方法
    
    SDWebImageOperation  //operation協(xié)議  只定義了cancel operation這一接口 上面的downloaderOperation的代理
    
    SDWebImagePrefetcher //低優(yōu)先級情況下預(yù)先下載圖片藐窄,對SDWebImageViewManager進(jìn)行簡單封裝 很少用
    MKAnnotationView+WebCache  //為MKAnnotationView異步加載圖片
    NSData+ImageContentType  //通過Image data判斷當(dāng)前圖片的格式
    UIButton+WebCache  //為UIButton異步加載圖片
    UIImage+GIF  //將Image data轉(zhuǎn)換成指定格式圖片
    UIImage+MultiFormat //將image data轉(zhuǎn)換成指定格式圖片
    UIImageView+HighlightedWebCache //為UIImageView異步加載圖片
    UIImageView+WebCache  //為UIImageView異步加載圖片
    UIView+WebCacheOperation  //保存當(dāng)前MKAnnotationView / UIButton / UIImageView異步下載圖片的operations
    
  1. SDWebImageOptions 所有選項(xiàng)
//默認(rèn)情況下,當(dāng)一個url下載失敗酬土,如果URL在黑名單中那么SDWebImage庫不進(jìn)行重試荆忍。這個標(biāo)志使黑名單失效。
SDWebImageRetryFailed = 1 << 0,

//默認(rèn)情況下撤缴,在UI交互中開始圖片下載刹枉,這個標(biāo)志使這個特性失效,例如導(dǎo)致在UIScrollView減速中延遲下載
SDWebImageLowPriority = 1 << 1,

//只進(jìn)行內(nèi)存緩存,這個標(biāo)志使硬盤緩存失效
SDWebImageCacheMemoryOnly = 1 << 2,

//這個標(biāo)志可以漸進(jìn)式下載,顯示的圖像是逐步在下載
SDWebImageProgressiveDownload = 1 << 3,

//刷新緩存
SDWebImageRefreshCached = 1 << 4,

//后臺下載腹泌,如果后臺任務(wù)時間過期那么操作將會被取消
SDWebImageContinueInBackground = 1 << 5,

//通過設(shè)置NSMutableURLRequest來操作cookies保存到NSHTTPCookieStore
//NSMutableURLRequest.HTTPShouldHandleCookies = YES;
SDWebImageHandleCookies = 1 << 6,

//允許使用無效的SSL證書,測試目的是有效的嘶卧。在生產(chǎn)環(huán)境被警告
SDWebImageAllowInvalidSSLCertificates = 1 << 7,

//默認(rèn)情況下尔觉,圖片在隊(duì)列中排隊(duì)下載凉袱。這個標(biāo)志移動他們到前面的隊(duì)列中
SDWebImageHighPriority = 1 << 8,

//延遲占位符
SDWebImageDelayPlaceholder = 1 << 9,

//我們通常在動畫圖片中不調(diào)用transformDownloadedImage代理,大部分的變形代碼將損壞圖片侦铜。使用這個標(biāo)志在任何情況下變形圖片
SDWebImageTransformAnimatedImage = 1 << 10
  
//默認(rèn)情況下专甩,圖片是在下載完成后加載到圖片視圖;使用這個標(biāo)志钉稍,如果你想在下載成功后在完成塊中手動設(shè)置圖片涤躲。
SDWebImageAvoidAutoSetImage = 1 << 11,

// 默認(rèn)情況下,圖片解碼為原始的大小贡未。在iOS种樱,這個標(biāo)志會把圖片縮小到與設(shè)備的受限內(nèi)容相兼容的大小。如果設(shè)置了SDWebImageProgressDownload標(biāo)志俊卤,那么縮小被設(shè)置為無效嫩挤。
SDWebImageScaleDownLargeImages = 1 << 12,

// 默認(rèn)情況下,當(dāng)圖像緩存在內(nèi)存中時消恍,我們不查詢磁盤數(shù)據(jù)岂昭。 此掩碼可以強(qiáng)制同時查詢磁盤數(shù)據(jù)。
// *建議將此標(biāo)志與`SDWebImageQueryDiskSync`一起使用狠怨,以確保圖像在同一個runloop中加載约啊。
SDWebImageQueryDataWhenInMemory = 1 << 13,

// 默認(rèn)情況下,我們同步查詢內(nèi)存緩存佣赖,異步查詢磁盤緩存恰矩。 此掩碼可以強(qiáng)制同步查詢磁盤緩存,以確保在同一個runloop中加載映像憎蛤。
// *如果禁用內(nèi)存緩存或在某些其他情況下枢里,此標(biāo)志可以避免在單元重用期間閃爍。
SDWebImageQueryDiskSync = 1 << 14,

// 默認(rèn)情況下,當(dāng)緩存丟失時栏豺,將從網(wǎng)絡(luò)下載映像彬碱。 此標(biāo)志可以阻止網(wǎng)絡(luò)僅從緩存加載。
SDWebImageFromCacheOnly = 1 << 15,

// 默認(rèn)情況下奥洼,在圖像加載完成后使用“SDWebImageTransition”進(jìn)行某些視圖轉(zhuǎn)換時巷疼,此轉(zhuǎn)換僅適用于從網(wǎng)絡(luò)下載圖像。 此掩碼也可以強(qiáng)制為內(nèi)存和磁盤緩存應(yīng)用視圖轉(zhuǎn)換灵奖。
SDWebImageForceTransition = 1 << 16

結(jié)構(gòu)圖

結(jié)構(gòu)圖

類API

類API

參考:一張圖片引發(fā)的深思

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嚼沿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓷患,更是在濱河造成了極大的恐慌骡尽,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件擅编,死亡現(xiàn)場離奇詭異攀细,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)爱态,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門谭贪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锦担,你說我怎么就攤上這事俭识。” “怎么了洞渔?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵套媚,是天一觀的道長。 經(jīng)常有香客問我磁椒,道長堤瘤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任衷快,我火速辦了婚禮宙橱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蘸拔。我一直安慰自己师郑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布调窍。 她就那樣靜靜地躺著宝冕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪邓萨。 梳的紋絲不亂的頭發(fā)上诺祸,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機(jī)與錄音缀壤,去河邊找鬼。 笑死洁闰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的万细。 我是一名探鬼主播扑眉,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赖钞!你這毒婦竟也來了腰素?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雪营,失蹤者是張志新(化名)和其女友劉穎弓千,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體献起,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洋访,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了征唬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捌显。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡茁彭,死狀恐怖总寒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情理肺,我是刑警寧澤摄闸,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站妹萨,受9級特大地震影響年枕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乎完,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一熏兄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧树姨,春花似錦摩桶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至转晰,卻和暖如春芦拿,著一層夾襖步出監(jiān)牢的瞬間士飒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工蔗崎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酵幕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓缓苛,卻偏偏與公主長得像裙盾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子他嫡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355