SDWebImage之圖片下載


title: SDWebImage之圖片下載
categories:

  • 第三方框架
    tags:
  • 三方框架解析

我們經(jīng)常使用SDWebImage在加載圖片苟穆,但對于圖片加載過程中怎么樣實(shí)現(xiàn)不會深究。下面我們就對SDWebImage進(jìn)行相應(yīng)的分析:
源碼地址

SDWebImage的下載器

SDWebImage的下載器是SDWebImageDownloader利用單例模式sharedDownloader,可以對下載的圖片進(jìn)行相關(guān)配置往衷。可以配置的部分如下:
<ul>
<li>下載選項(xiàng)</li>
<li>HTTP的頭部</li>
<li>壓縮布隔、下載超時(shí)霎匈、下載順序、最大并發(fā)數(shù)等</li>
</ul>

下載選項(xiàng)

<pre>
<code>
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {

//下載的優(yōu)先級

SDWebImageDownloaderLowPriority = 1 << 0,

//下載進(jìn)度

SDWebImageDownloaderProgressiveDownload = 1 << 1,

//下載路徑緩存

SDWebImageDownloaderUseNSURLCache = 1 << 2,

//下載過程的請求緩存

SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,

//后臺進(jìn)行繼續(xù)下載

SDWebImageDownloaderContinueInBackground = 1 << 4,

SDWebImageDownloaderHandleCookies = 1 << 5,

SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

SDWebImageDownloaderHighPriority = 1 << 7,
};
</code>
</pre>完

HTTP的頭部設(shè)置

<pre>
<code>

ifdef SD_WEBP

    _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];

else

    _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];

endif

-(void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {

if (value) {
    self.HTTPHeaders[field] = value;
}
else {
    [self.HTTPHeaders removeObjectForKey:field];
}

}

-(NSString *)valueForHTTPHeaderField:(NSString *)field {

return self.HTTPHeaders[field];

}</code>
</pre>我們可以通過上述forHTTPHeaderField的參數(shù)進(jìn)行相應(yīng)HTTPheader的設(shè)置乱投,使用者可以對頭部信息進(jìn)行相關(guān)的添加或者是刪除HTTP頭部信息咽笼。

線程安全

在圖片下載過程中我們要保線程訪問的安全性,barrierQueue是實(shí)現(xiàn)網(wǎng)絡(luò)響應(yīng)的序列化實(shí)例戚炫。
<pre>
<code>
// This queue is used to serialize the handling of the network responses of all

the download operation in a single queue

@property (SDDispatchQueueSetterSementics, nonatomic)

dispatch_queue_t barrierQueue;
</code>
</pre>在保證線程安全的起見剑刑,我們對于URLCallbacks進(jìn)行增改都需要放在dispatch_barrier_sync的形式放入到barrierQueue。但是如果我們只要進(jìn)行相關(guān)的查詢那就使用dispatch_sync放入barrierQueue中即可双肤。
<pre><code>
__block NSArray *callbacksForURL;

dispatch_sync(sself.barrierQueue, ^{

callbacksForURL = [sself.URLCallbacks[url] copy];

});

dispatch_barrier_sync(self.barrierQueue, ^{

BOOL first = NO;
if (!self.URLCallbacks[url]) {
    self.URLCallbacks[url] = [NSMutableArray new];
    first = YES;
}

// Handle single download of simultaneous download request for the same URL

NSMutableArray *callbacksForURL = self.URLCallbacks[url];

NSMutableDictionary *callbacks = [NSMutableDictionary new];

if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];

if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];

[callbacksForURL addObject:callbacks];

self.URLCallbacks[url] = callbacksForURL;

if (first) {

  createCallback();

}
});</code></pre>完

回調(diào)

在我們下載圖片的過程中施掏,每一張圖片都需要開啟一個(gè)線程,在每一個(gè)線程中都需要對執(zhí)行一定的回調(diào)信息茅糜。這些回調(diào)的信息會以block的實(shí)行出現(xiàn):
<pre>
<code>
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize,
NSInteger expectedSize);

typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data,
NSError *error, BOOL finished);

typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url,
NSDictionary *headers);
</code>
</pre>圖片下載的這些回調(diào)信息存儲在SDWebImageDownloader類的URLCallbacks屬性中七芭,該屬性是一個(gè)字典,key是圖片的URL地址蔑赘,value則是一個(gè)數(shù)組狸驳,包含每個(gè)圖片的多組回調(diào)信息。

下載器

整個(gè)下載過程中我們需要執(zhí)行在本小結(jié)講述的下載器中進(jìn)行米死,下載器對于下載的管理都是放在-(id <SDWebImageOperation>)downloadImageWithURL:中的:
<pre>
<code>

  • (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __weak __typeof(self)wself = self;
    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
    NSTimeInterval timeoutInterval = wself.downloadTimeout;
    if (timeoutInterval == 0.0) {
    timeoutInterval = 15.0;
    }

// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ?
NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData)
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
operation = [[wself.operationClass alloc] initWithRequest:request
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
operation.shouldDecompressImages = wself.shouldDecompressImages;

  if (wself.urlCredential) {
      operation.credential = wself.urlCredential;
  } else if (wself.username && wself.password) {
      operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
  }
  
  if (options & SDWebImageDownloaderHighPriority) {
      operation.queuePriority = NSOperationQueuePriorityHigh;
  } else if (options & SDWebImageDownloaderLowPriority) {
      operation.queuePriority = NSOperationQueuePriorityLow;
  }

  [wself.downloadQueue addOperation:operation];
  if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
      // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
      [wself.lastAddedOperation addDependency:operation];
      wself.lastAddedOperation = operation;
  }

}];

return operation;
}
</code>
</pre>我們在上面的方法中調(diào)用的方法(void)addProgressCallback:completedBlock:forURL:createCallback:將在訪問圖片請求的信息直接放入下載器锌历。
<pre>
<code>

  • (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL*)url createCallback:(SDWebImageNoParamsBlock)createCallback {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    if (url == nil) {
    if (completedBlock != nil) {
    completedBlock(nil, nil, nil, NO);
    }
    return;
    }
    dispatch_barrier_sync(self.barrierQueue, ^{
    BOOL first = NO;
    if (!self.URLCallbacks[url]) {
    self.URLCallbacks[url] = [NSMutableArray new];
    first = YES;
    }

    // Handle single download of simultaneous download request for the same URL
    NSMutableArray *callbacksForURL = self.URLCallbacks[url];
    NSMutableDictionary *callbacks = [NSMutableDictionary new];
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    [callbacksForURL addObject:callbacks];
    self.URLCallbacks[url] = callbacksForURL;

    if (first) {
    createCallback();
    }
    });
    }
    </code>
    </pre>完

下載操作

在每張圖片下載過程中都要調(diào)用一次具體的操作都會調(diào)用Operation,下面就分析一下中間的具體過程峦筒。
我們打開SDWebImage的文件夾可以到其中有一個(gè)SDWebImageOperation的類究西,如下:
<pre>
<code>

import <Foundation/Foundation.h>

@protocol SDWebImageOperation <NSObject>

-(void)cancel;

@end
</code>
</pre>其中我們使用NSOpation的子類來完成具體圖片下載的過程,這個(gè)類就是SDWebImageDownloaderOperation物喷。在SDWebImageDownloaderOperation類中繼承NSOperation的類而且實(shí)現(xiàn)SDWebImageOperation的cancel的取消協(xié)議卤材。除了繼承而來的方法遮斥,該類只向外暴露了一個(gè)方法,即上面所用到的初始化方法initWithRequest:options:pregress:completed:cancelled:扇丛。

對于圖片的下載术吗,SDWebImageDownloaderOperation完全依賴于URL加載系統(tǒng)中的NSURLConnection類(并未使用iOS7以后的NSURLSession類)。我們先來分析一下SDWebImageDownloaderOperation類中對于圖片實(shí)際數(shù)據(jù)的下載處理帆精,即NSURLConnection各代理方法的實(shí)現(xiàn)较屿。

首先,SDWebImageDownloaderOperation在Extention中采用了NSURLConnectionDataDelegate協(xié)議卓练,并實(shí)現(xiàn)了協(xié)議的以下幾個(gè)方法:

<pre>
<code>
connection:didReceiveResponse:

connection:didReceiveData:

connectionDidFinishLoading:

connection:didFailWithError:

connection:willCacheResponse:

connectionShouldUseCredentialStorage:

connection:willSendRequestForAuthenticationChallenge:
</code>
</pre>這些方法我們就不逐一分析了隘蝎,就終點(diǎn)分析一下connection:didReceiveResponse:和connection:didReceiveData:兩個(gè)方法。

connection:didReceiveResponse方法通過判斷NSURLResponse的實(shí)際類型和狀態(tài)碼襟企,對除304以外400以內(nèi)的狀態(tài)碼反應(yīng)嘱么。

<pre>
<code>

  • (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

//'304 Not Modified' is an exceptional one
if (![response respondsToSelector:@selector(statusCode)] ||([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)
response.expectedContentLength : 0;
self.expectedSize = expected;
if (self.progressBlock) {
self.progressBlock(0, expected);
}

self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:
SDWebImageDownloadReceiveResponseNotification object:self];
});
}
else {
NSUInteger code = [((NSHTTPURLResponse *)response) statusCode];

//This is the case when server returns '304 Not Modified'. It means that
remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image
from the cache.
if (code == 304) {
[self cancelInternal];
} else {
[self.connection cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});

if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:
NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode]
userInfo:nil], YES);
}
CFRunLoopStop(CFRunLoopGetCurrent());
[self done];
}
}
</code>
</pre>connection:didReceiveData:方法的主要任務(wù)是接受數(shù)據(jù)。每次接收到數(shù)據(jù)時(shí)顽悼,都會用現(xiàn)有的數(shù)據(jù)創(chuàng)建一個(gè)CGImageSourceRef對象以作處理曼振。在首次獲取到數(shù)據(jù)時(shí)(width+height==0)會從這些包含圖像信息的數(shù)據(jù)中取出圖像的長、寬蔚龙、方向等信息以備使用冰评。而后在圖片下載完成之前,會使用CGImageSourceRef對象創(chuàng)建一個(gè)圖像對象木羹,經(jīng)過縮放集索、解壓縮操作后生成一個(gè)UIImage對象供完成回調(diào)使用。當(dāng)然汇跨,在這個(gè)方法中還需要處理的就是進(jìn)度信息。如果我們有設(shè)置進(jìn)度回調(diào)的話妆距,就調(diào)用進(jìn)度回調(diào)以處理當(dāng)前圖片的下載進(jìn)度穷遂。

<pre>
<code>

  • (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
    [self.imageData appendData:data];

    if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
    // The following code is from http://www.cocoaintheshell.com/2011/05/
    progressive-images-download-imageio/
    // Thanks to the author @Nyx0uf

// Get the total bytes downloaded
const NSInteger totalSize = self.imageData.length;

// Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);

    if (width + height == 0) {
        CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
        if (properties) {
            NSInteger orientationValue = -1;
            CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
            if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
            val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
            if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
            val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
            if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
            CFRelease(properties);

// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in connectionDidFinishLoading.) So save it here and pass it on later.

            orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
        }

    }

    if (width + height > 0 && totalSize < self.expectedSize) {
        // Create the image
        CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);

ifdef TARGET_OS_IPHONE

        // Workaround for iOS anamorphic image
        if (partialImageRef) {
            const size_t partialHeight = CGImageGetHeight(partialImageRef);
            CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
            CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
            CGColorSpaceRelease(colorSpace);
            if (bmContext) {
                CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
                CGImageRelease(partialImageRef);
                partialImageRef = CGBitmapContextCreateImage(bmContext);
                CGContextRelease(bmContext);
            }
            else {
                CGImageRelease(partialImageRef);
                partialImageRef = nil;
            }
        }

endif

        if (partialImageRef) {
            UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
            UIImage *scaledImage = [self scaledImageForKey:key image:image];
            if (self.shouldDecompressImages) {
                image = [UIImage decodedImageWithImage:scaledImage];
            }
            else {
                image = scaledImage;
            }
            CGImageRelease(partialImageRef);
            dispatch_main_sync_safe(^{
                if (self.completedBlock) {
                    self.completedBlock(image, nil, nil, NO);
                }
            });
        }
    }

    CFRelease(imageSource);
}

if (self.progressBlock) {
    self.progressBlock(self.imageData.length, self.expectedSize);
}

}
</code>
</pre>我們前面說過SDWebImageDownloaderOperation類是繼承自NSOperation類。它沒有簡單的實(shí)現(xiàn)main方法娱据,而是采用更加靈活的start方法蚪黑,以便自己管理下載的狀態(tài)。

在start方法中中剩,創(chuàng)建了我們下載所使用的NSURLConnection對象忌穿,開啟了圖片的下載,同時(shí)拋出一個(gè)下載開始的通知结啼。start方法的具體實(shí)現(xiàn)如下:
<pre>
<code>

  • (void)start {
    @synchronized (self) {
    if (self.isCancelled) {
    self.finished = YES;
    [self reset];
    return;
    }

if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0

    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
    if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
        __weak __typeof__ (self) wself = self;
        UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
        self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
            __strong __typeof (wself) sself = wself;

            if (sself) {
                [sself cancel];

                [app endBackgroundTask:sself.backgroundTaskId];
                sself.backgroundTaskId = UIBackgroundTaskInvalid;
            }
        }];
    }

endif

    self.executing = YES;
    self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
    self.thread = [NSThread currentThread];
}

[self.connection start];

if (self.connection) {
    if (self.progressBlock) {
        self.progressBlock(0, NSURLResponseUnknownLength);
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
    });

    if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
        // Make sure to run the runloop in our background thread so it can process downloaded data
        // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5
        //       not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
    }
    else {
        CFRunLoopRun();
    }

    if (!self.isFinished) {
        [self.connection cancel];
        [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
    }
}
else {
    if (self.completedBlock) {
        self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
    }
}

if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0

Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
    return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
    UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
    [app endBackgroundTask:self.backgroundTaskId];
    self.backgroundTaskId = UIBackgroundTaskInvalid;
}

endif

}
</code>
</pre>在下載完后或者是下載失敗后都會停止當(dāng)前調(diào)用的runloop掠剑,清楚鏈接隨后就拋出下載停止的消息。
如果下載成功郊愧,則會處理完整的圖片數(shù)據(jù)朴译,對其進(jìn)行適當(dāng)?shù)目s放與解壓縮操作井佑,以提供給完成回調(diào)使用。具體可參考-connectionDidFinishLoading:與-connection:didFailWithError:的實(shí)現(xiàn)眠寿。

總結(jié)

我們在上面的介紹可以看出在下載圖片的過程中躬翁,每次下載圖片都會調(diào)用NSOperation的函數(shù)進(jìn)行處理,每次數(shù)據(jù)實(shí)際實(shí)現(xiàn)是使用NSURLConnection盯拱。我們把具體實(shí)現(xiàn)的線程放置在隊(duì)列中進(jìn)行執(zhí)行操作盒发。如果下載成功,則會處理完整的圖片數(shù)據(jù)狡逢,對其進(jìn)行適當(dāng)?shù)目s放與解壓縮操作宁舰,以提供給完成回調(diào)使用。具體可參考-connectionDidFinishLoading:與-connection:didFailWithError:的實(shí)現(xiàn)甚侣。

下一節(jié)我們將講述如何對下載的圖片進(jìn)行緩存處理;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末明吩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子殷费,更是在濱河造成了極大的恐慌印荔,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件详羡,死亡現(xiàn)場離奇詭異仍律,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)实柠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門水泉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窒盐,你說我怎么就攤上這事草则。” “怎么了蟹漓?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵炕横,是天一觀的道長。 經(jīng)常有香客問我葡粒,道長份殿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任嗽交,我火速辦了婚禮卿嘲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘夫壁。我一直安慰自己拾枣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布盒让。 她就那樣靜靜地躺著放前,像睡著了一般忿磅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凭语,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天葱她,我揣著相機(jī)與錄音,去河邊找鬼似扔。 笑死吨些,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炒辉。 我是一名探鬼主播豪墅,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼黔寇!你這毒婦竟也來了偶器?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缝裤,失蹤者是張志新(化名)和其女友劉穎屏轰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憋飞,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霎苗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了榛做。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唁盏。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖检眯,靈堂內(nèi)的尸體忽然破棺而出厘擂,到底是詐尸還是另有隱情,我是刑警寧澤锰瘸,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布驴党,位于F島的核電站,受9級特大地震影響获茬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜倔既,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一恕曲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渤涌,春花似錦佩谣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吊履。三九已至,卻和暖如春调鬓,著一層夾襖步出監(jiān)牢的瞬間艇炎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工腾窝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缀踪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓虹脯,卻偏偏與公主長得像驴娃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子循集,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

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