圖片加載 經(jīng)典的UIL框架

因為情懷,所以很想研究下這個框架...
先梳理一下Java知識

在Java里, 當(dāng)一個對象o被創(chuàng)建時, 它被放在Heap(堆內(nèi)存)里. 當(dāng)GC運行的時候, 如果發(fā)現(xiàn)沒有任何引用指向o, o就會被回收以騰出內(nèi)存空間. 或者換句話說, 一個對象被回收, 必須滿足兩個條件:
1)沒有任何引用指向它
2)GC被運行.
在現(xiàn)實情況寫代碼的時候, 我們往往通過把所有指向某個對象的referece置空來保證這個對象在下次GC運行的時候被回收
為了減少咱們開發(fā)者去手動置空對象,Java中引入了主要的WeakReference和SoftReference

弱引用特性:

**當(dāng)一個對象僅僅被WeakReference指向, 而沒有任何其他StrongReference指向的時候, 如果GC運行,不管當(dāng)前內(nèi)存空間足夠與否, 那么這個對象就會被回收. **

軟引用特性:

如果一個對象只具有軟引用冷溶,則內(nèi)存空間足夠蝶怔,垃圾回收器就不會回收它享潜;如果內(nèi)存空間不足了哮幢,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它料祠,該對象就可以被程序使用惑申。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存.

好了,有了這些概念,可以向下看了!
先看看加載圖片主入口

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
            ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)  

imageAware是一個包裝類ImageViewAware對象,父類是ViewAware,可以看到它內(nèi)部維護(hù)的是一個ImageView的弱引用對象:

public ViewAware(View view, boolean checkActualViewSize) {
        if (view == null) throw new IllegalArgumentException("view must not be null");

        this.viewRef = new WeakReference<View>(view);
        this.checkActualViewSize = checkActualViewSize;
    }

ImageSize是一個對寬度和高度的包裝
加載過程:
1.如果uri地址為空,那么ImageLoaderEngine取消顯示任務(wù)

void cancelDisplayTaskFor(ImageAware imageAware) {
        cacheKeysForImageAwares.remove(imageAware.getId());
}

并將空圖片設(shè)置給ImageView或者不顯示圖片,任務(wù)顯示完成,返回return
2.地址不為空,繼續(xù)向下執(zhí)行,首先檢測尺寸大小,如果大小未指定就設(shè)置屏幕的尺寸

if (targetSize == null) {
            targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
        }

3.繼續(xù)向下執(zhí)行,生成緩存key,并將imageAware和key一一對應(yīng)關(guān)系緩存起來

String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

這個key是地址和大小的拼湊

Pattern for cache key - <b>[imageUri]_[width]x[height]</b>.

4.繼續(xù)向下,根據(jù)key從內(nèi)存緩存中取bitmap

Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);

而這個memoryCache是初始化ImageLoaderConfiguration的時候配置的,它在什么時候被實例化的呢?
UIL初始化的時候是這么用的

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)  
        .memoryCacheExtraOptions(480, 800) // default = device screen dimensions  
        .diskCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null)  
        .taskExecutor(...)  
        .taskExecutorForCachedImages(...)  
        .threadPoolSize(3) // default  
        .threadPriority(Thread.NORM_PRIORITY - 1) // default  
        .tasksProcessingOrder(QueueProcessingType.FIFO) // default  
        .denyCacheImageMultipleSizesInMemory()  
        .memoryCache(new LruMemoryCache(2 * 1024 * 1024))  
        .memoryCacheSize(2 * 1024 * 1024)  
        .memoryCacheSizePercentage(13) // default  
        .diskCache(new UnlimitedDiscCache(cacheDir)) // default  
        .diskCacheSize(50 * 1024 * 1024)  
        .diskCacheFileCount(100)  
        .build();  

是不是建造者模式,在build()的時候

public ImageLoaderConfiguration build() {
    initEmptyFieldsWithDefaultValues();
    return new ImageLoaderConfiguration(this);
}

初始化空值檢測

if (memoryCache == null) {
    memoryCache = DefaultConfigurationFactory.createMemoryCache(context, memoryCacheSize);
}

如果為空,也就是初始化的時候沒有指定實例對象,那么就賦值為默認(rèn)的內(nèi)存緩存對象,工廠模式來了

public static MemoryCache createMemoryCache(Context context, int memoryCacheSize) {
        if (memoryCacheSize == 0) {
            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            int memoryClass = am.getMemoryClass();
            if (hasHoneycomb() && isLargeHeap(context)) {
                memoryClass = getLargeMemoryClass(am);
            }
            memoryCacheSize = 1024 * 1024 * memoryClass / 8;
        }
        return new LruMemoryCache(memoryCacheSize);
    }

原來默認(rèn)是LruMemoryCache緩存,默認(rèn)大小是1/8的可用內(nèi)存空間,似乎這已經(jīng)成為行業(yè)標(biāo)準(zhǔn)!


UIL框架一共提供了8種緩存策略,現(xiàn)在知道LruMemoryCache是默認(rèn)的,它內(nèi)部維護(hù)的是一個LinkedHashMap<String, Bitmap> map容器,
map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
每次訪問的時候都會重新排序,把accessOrder設(shè)為true,也就是基于訪問順序 排序,每次訪問get或者put,都會把最不常用的移到表頭,如果超過了內(nèi)存限制,會把最不常訪問的那個刪掉,也就是從表頭移除remove


這里從內(nèi)存緩存中拿出了bitmap
1.如果不為空,沒有被回收
1.1 然后判斷options.shouldPostProcess(),是否需要額外處理bitmap
如果為true,判斷是同步還是異步

ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                        options, listener, progressListener, engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
                        defineHandler(options));
if (options.isSyncLoading()) {
    displayTask.run();
} else {
    engine.submit(displayTask);
}

ProcessAndDisplayImageTask是一個Runnable 對象,如果是異步操作的話,這個任務(wù)會被分配到線程池中執(zhí)行taskExecutorForCachedImages.execute(task);
看一下線程池的初始化

 private Executor createTaskExecutor() {
         return DefaultConfigurationFactory
                .createExecutor(configuration.threadPoolSize, configuration.threadPriority,
                configuration.tasksProcessingType);
    }

線程池的大小,優(yōu)先級執(zhí)行策略的初始值,默認(rèn)最多3個線程同時,先進(jìn)先出的任務(wù)調(diào)度原則

private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE;
private int threadPriority = DEFAULT_THREAD_PRIORITY;
private QueueProcessingType tasksProcessingType = DEFAULT_TASK_PROCESSING_TYPE;
public static final QueueProcessingType DEFAULT_TASK_PROCESSING_TYPE = QueueProcessingType.FIFO;
public static final int DEFAULT_THREAD_POOL_SIZE = 3;
public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 2;

工廠生產(chǎn)線程池執(zhí)行器過程:

public static Executor createExecutor(int threadPoolSize, int threadPriority,
            QueueProcessingType tasksProcessingType) {
        boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;
        BlockingQueue<Runnable> taskQueue =
                lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
        return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
                createThreadFactory(threadPriority, "uil-pool-"));
    }

這里隊列區(qū)分了后進(jìn)先出LIFO這個特殊模式!
回到異步執(zhí)行,run方法執(zhí)行

 @Override
    public void run() {
        BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
        Bitmap processedBitmap = processor.process(bitmap);
        DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
                LoadedFrom.MEMORY_CACHE);
        LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
    }

processor加工處理完bitmap后,交給顯示任務(wù)LoadAndDisplayImageTask顯示

@Override
    public void run() {
        if (imageAware.isCollected()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else if (isViewWasReused()) {
            L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED, memoryCacheKey);
            listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
        } else {
            L.d(LOG_DISPLAY_IMAGE_IN_IMAGEAWARE, loadedFrom, memoryCacheKey);
            displayer.display(bitmap, imageAware, loadedFrom);
            engine.cancelDisplayTaskFor(imageAware);
            listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
        }
    }

如果正常顯示,是交給了displayer這個顯示器,而這個顯示器默認(rèn)也是配置工廠生產(chǎn)的

public static BitmapDisplayer createBitmapDisplayer() {
        return new SimpleBitmapDisplayer();
}

字面理解就是最簡單的顯示器,看看它的內(nèi)容

public final class SimpleBitmapDisplayer implements BitmapDisplayer {
    @Override
    public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
        imageAware.setImageBitmap(bitmap);
    }
}

直接imageview顯示bitmap



BitmapDisplayer是父類,那么一共有5種顯示器,還有圓形的,圓角的,漸變的等顯示器,比較豐富!
1.2回到options.shouldPostProcess(),如果為false,也就是不需要處理從內(nèi)存中取出的bitmap,那么就直接交給顯示器顯示

 options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);

2.如果從內(nèi)存中取出的bitmap為空或者已經(jīng)被回收了呢?繼續(xù)向下看碼

           if (options.shouldShowImageOnLoading()) {
                imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
            } else if (options.isResetViewBeforeLoading()) {
                imageAware.setImageDrawable(null);
            }

            ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
                    options, listener, progressListener, engine.getLockForUri(uri));
            LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
                    defineHandler(options));
            if (options.isSyncLoading()) {
                displayTask.run();
            } else {
                engine.submit(displayTask);
            }

首先從配置信息中判斷是否需要在加載的時候顯示自定義加載圖片,為了體驗更好通常會顯示Loading的Icon,好了,進(jìn)入核心代碼,準(zhǔn)備從本地磁盤或者網(wǎng)絡(luò)下載圖片 ,這里和上面一樣,也是區(qū)分同步還是異步操作
2.1 如果是同步的,直接執(zhí)行run()方法
一行行的看

if (waitIfPaused()) return; 
if (delayIfNeed()) return;

首先判斷一些狀態(tài),有這么幾種情況,會返回
(1)如果任務(wù)被中斷了interrupted
(2)如果ImageAware被GC回收了
(3)如果image的地址和任務(wù)的地址不一致


ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
loadFromUriLock.lock();

獲取鎖,從內(nèi)存中獲取bitmap

bmp = configuration.memoryCache.get(memoryCacheKey);

如果bmp是不可用的,加載本地圖片

bmp = tryLoadBitmap();
File imageFile = configuration.diskCache.get(uri);

首先從硬盤中取圖片文件,如果沒有的話tryCacheImageOnDisk(),就嘗試去網(wǎng)絡(luò)下載然后緩存到磁盤上,跟進(jìn)去

loaded = downloadImage();
if (loaded) {
    int width = configuration.maxImageWidthForDiskCache;
    int height = configuration.maxImageHeightForDiskCache;
    if (width > 0 || height > 0) {
        L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);
        resizeAndSaveImage(width, height); // TODO : process boolean result
    }
}
private boolean downloadImage() throws IOException {
        InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
        if (is == null) {
            L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
            return false;
        } else {
            try {
                return configuration.diskCache.save(uri, is, this);
            } finally {
                IoUtils.closeSilently(is);
            }
        }
    }

拿到流后給diskCache硬盤緩存保存,默認(rèn)的文件名生成器是HashCodeFileNameGenerator 默認(rèn)的磁盤緩存器是UnlimitedDiskCache,無限制的緩存

緩存目錄:/Android/data/[app_package_name]/cache

Hash文件名生成

public class HashCodeFileNameGenerator implements FileNameGenerator {
    @Override
    public String generate(String imageUri) {
        return String.valueOf(imageUri.hashCode());
    }
}

這里下載緩存到本地之后,resizeAndSaveImage執(zhí)行,把bitmap從剛才緩存的文件中讀上來,調(diào)整大小,然后二次保存!


好,disk緩存工作全部結(jié)束,然后要讀圖片,加載到內(nèi)存并且顯示

bitmap = decodeImage(imageUriForDecoding);
private Bitmap decodeImage(String imageUri) throws IOException {
        ViewScaleType viewScaleType = imageAware.getScaleType();
        ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
                getDownloader(), options);
        return decoder.decode(decodingInfo);
    }

這里包裝了一個ImageDecodingInfo對象,倒數(shù)第二個參數(shù)是一個下載器對象,跟進(jìn)去看看

private ImageDownloader getDownloader() {
        ImageDownloader d;
        if (engine.isNetworkDenied()) {
            d = networkDeniedDownloader;
        } else if (engine.isSlowNetwork()) {
            d = slowNetworkDownloader;
        } else {
            d = downloader;
        }
        return d;
    }

networkDeniedDownloader指向的是NetworkDeniedImageDownloader,它是一個裝飾器Decorator(包裹了ImageDownloader對象), 從1.8.0版本開始有了這個類,目的是為了阻止從網(wǎng)絡(luò)下載圖片,看看它的getStream()方法,根據(jù)加載地址判斷,如果是http和https開頭,就直接拋出異常了,那么只能從本地資源加載了!
slowNetworkDownloader指向的是SlowNetworkImageDownloader,同樣是一個裝飾器Decorator,它為了處理比較慢的網(wǎng)絡(luò)情況,如訪問
http://code.google.com/p/android/issues/detail?id=6066這樣的地址
downloader可以由開發(fā)者外部定義傳入,或者是默認(rèn)的,BaseImageDownloader這個對象,好了3種下載器都介紹了!繼續(xù)往下看!


解碼器decoder要工作了decoder.decode(decodingInfo)
這個decoder可以開發(fā)者從外部定義傳入,默認(rèn)值是BaseImageDecoder
跟進(jìn)去,看如何decode的

 public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
        Bitmap decodedBitmap;
        ImageFileInfo imageInfo;

        InputStream imageStream = getImageStream(decodingInfo);
        if (imageStream == null) {
            L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
            return null;
        }
        try {
            imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
            imageStream = resetStream(imageStream, decodingInfo);
            Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
            decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
        } finally {
            IoUtils.closeSilently(imageStream);
        }

        if (decodedBitmap == null) {
            L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
        } else {
            decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
                    imageInfo.exif.flipHorizontal);
        }
        return decodedBitmap;
    }

首先是獲得網(wǎng)絡(luò)流InputStream,這里只分析默認(rèn)的情況BaseImageDownloader下載器

public InputStream getStream(String imageUri, Object extra) throws IOException {
        switch (Scheme.ofUri(imageUri)) {
            case HTTP:
            case HTTPS:
                return getStreamFromNetwork(imageUri, extra);
            case FILE:
                return getStreamFromFile(imageUri, extra);
            case CONTENT:
                return getStreamFromContent(imageUri, extra);
            case ASSETS:
                return getStreamFromAssets(imageUri, extra);
            case DRAWABLE:
                return getStreamFromDrawable(imageUri, extra);
            case UNKNOWN:
            default:
                return getStreamFromOtherSource(imageUri, extra);
        }
    }

根據(jù)地址區(qū)分圖片的源頭,分析網(wǎng)絡(luò)流

protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
        HttpURLConnection conn = createConnection(imageUri, extra);

        int redirectCount = 0;
        while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
            conn = createConnection(conn.getHeaderField("Location"), extra);
            redirectCount++;
        }

        InputStream imageStream;
        try {
            imageStream = conn.getInputStream();
        } catch (IOException e) {
            // Read all data to allow reuse connection (http://bit.ly/1ad35PY)
            IoUtils.readAndCloseStream(conn.getErrorStream());
            throw e;
        }
        if (!shouldBeProcessed(conn)) {
            IoUtils.closeSilently(imageStream);
            throw new IOException("Image request failed with response code " + conn.getResponseCode());
        }

        return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
    }

這里如果連接不能建立的話,會重連5次,達(dá)到5次就不繼續(xù)連接了,這里網(wǎng)絡(luò)用的是HttpURLConnection,最后返回ContentLengthInputStream這個流,又是一個包裝類,繼承自InputStream, 這個流是從1.9.1版本開始有的,她的解釋是可以獲取自定義長度的流,她通過重寫int available()方法來限制讀取length長度的內(nèi)容,而這個長度可以看到是conn.getContentLength(),也就是文件大小
繼續(xù)看碼,拿到流了,定義文件大小和圖片旋轉(zhuǎn)方向

imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);

補充一句獲取bitmap的時候盡量用BitmapFactory.decodeStream而不要用decodeFile\decodeResource之類的

protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)
            throws IOException {
        Options options = new Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(imageStream, null, options);

        ExifInfo exif;
        String imageUri = decodingInfo.getImageUri();
        if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {
            exif = defineExifOrientation(imageUri);
        } else {
            exif = new ExifInfo();
        }
        return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);
    }

ExifInfo保存了圖片的一些旋轉(zhuǎn)岩睁、翻轉(zhuǎn)等信息,接下來因為已經(jīng)讀了一次流InputStream取到了圖片大小信息,后面還要讀流,怎么辦呢?
答案是重置流resetStream

if (imageStream.markSupported()) {
    try {
        imageStream.reset();
        return imageStream;
        } catch (IOException ignored) {
    }
 }
 IoUtils.closeSilently(imageStream);
 return getImageStream(decodingInfo);

首先判斷流是否支持標(biāo)記mark,如果支持就可以調(diào)用reset()方法重置,默認(rèn)的InputStream是不支持的,也就是只能讀一次,輸入管道內(nèi)容就沒了,但是當(dāng)前這個流是
ContentLengthInputStream這個包裝對象,它重寫了markSupported

@Override
public boolean markSupported() {
    return stream.markSupported();
}

退回到獲取流的時候,最后ContentLengthInputStream包裹的是BufferedInputStream緩沖輸入流,而她是支持reset()的
當(dāng)然如果包裹的是其它不支持標(biāo)記的流,這里 往下執(zhí)行,就要重新建立連接獲得流對象了,getImageStream(decodingInfo)再次完成建立連接,獲取Inputstream!
此時就可以通過流去獲取bitmap對象了

decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions)

拿到原始bitmap還不能馬上返回,還要根據(jù)上面獲得的圖片大小,旋轉(zhuǎn),翻轉(zhuǎn)等信息,看看bitmap是否需要二次加工,涉及Matrix矩陣變換...

decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
                    imageInfo.exif.flipHorizontal)

加工完成后bitmap返回,繼續(xù)回到LoadAndDisplayImageTask的run方法里,向下執(zhí)行

if (bmp != null && options.isCacheInMemory()) {
                    L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
                    configuration.memoryCache.put(memoryCacheKey, bmp);
                }

這里判斷一下是否需要緩存到內(nèi)存

最后一步

DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
        runTask(displayBitmapTask, syncLoading, handler, engine);

啟動了一個任務(wù),交給顯示器displayer顯示

public void run() {
   ...
   displayer.display(bitmap, imageAware, loadedFrom);
}

基本上一個請求的過程就算分析完了,最后看一下整體流程圖

這里下載器,解碼器,Bitmap處理器,顯示器都可以自己制定.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市燥狰,隨后出現(xiàn)的幾起案子棘脐,更是在濱河造成了極大的恐慌,老刑警劉巖龙致,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛀缝,死亡現(xiàn)場離奇詭異,居然都是意外死亡目代,警方通過查閱死者的電腦和手機屈梁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榛了,“玉大人在讶,你說我怎么就攤上這事∷螅” “怎么了构哺?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長战坤。 經(jīng)常有香客問我遮婶,道長,這世上最難降的妖魔是什么湖笨? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蹦骑,結(jié)果婚禮上慈省,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好边败,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布袱衷。 她就那樣靜靜地躺著,像睡著了一般笑窜。 火紅的嫁衣襯著肌膚如雪致燥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天排截,我揣著相機與錄音嫌蚤,去河邊找鬼。 笑死断傲,一個胖子當(dāng)著我的面吹牛脱吱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播认罩,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼箱蝠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垦垂?” 一聲冷哼從身側(cè)響起宦搬,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎劫拗,沒想到半個月后间校,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡杨幼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年撇簿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片差购。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡四瘫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出欲逃,到底是詐尸還是另有隱情找蜜,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布稳析,位于F島的核電站洗做,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏彰居。R本人自食惡果不足惜诚纸,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陈惰。 院中可真熱鬧畦徘,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至杯缺,卻和暖如春蒸播,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萍肆。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工袍榆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匾鸥。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓蜡塌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勿负。 傳聞我的和親對象是個殘疾皇子馏艾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

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