Android開(kāi)源框架Universal-Image-Loader緩存機(jī)制淺析

緩存

提高用戶體驗(yàn)蚜印,同時(shí)也使得應(yīng)用更加流暢,也就是緩存圖片至內(nèi)存時(shí)留量,可以更加高效的工作窄赋。

配置

在應(yīng)用中配置ImageLoaderConfiguration參數(shù)(注意:只配置一次就好了哟冬,如多次配置,則默認(rèn)第一次的配置參數(shù))

默認(rèn)設(shè)置(即框架已配置好了參數(shù))

ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);

自定義設(shè)置

File cacheDir = StorageUtils.getCacheDirectory(context);  //緩存文件夾路徑
    ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
            .memoryCacheExtraOptions(480, 800) // default = device screen dimensions 內(nèi)存緩存文件的最大長(zhǎng)寬
           // .diskCacheExtraOptions(480, 800, null)  // 本地緩存的詳細(xì)信息(緩存的最大長(zhǎng)寬)忆绰,最好不要設(shè)置這個(gè) 
            .taskExecutor(...)
            .taskExecutorForCachedImages(...)
            .threadPoolSize(5) // default是3個(gè)線程池  線程池內(nèi)加載的數(shù)量
            .threadPriority(Thread.NORM_PRIORITY - 2) // default 設(shè)置當(dāng)前線程的優(yōu)先級(jí)
            .tasksProcessingOrder(QueueProcessingType.FIFO) // default
            .denyCacheImageMultipleSizesInMemory()
            .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) //可以通過(guò)自己的內(nèi)存緩存實(shí)現(xiàn)
            .memoryCacheSize(2 * 1024 * 1024)  // 內(nèi)存緩存的最大值
            .memoryCacheSizePercentage(13) // default
            .diskCache(new UnlimitedDiscCache(cacheDir)) // default 浩峡,可以自定義緩存路徑  
            .diskCacheSize(50 * 1024 * 1024) // 50 Mb sd卡(本地)緩存的最大值
            .diskCacheFileCount(100)  // 可以緩存的文件數(shù)量 
            // default為使用HASHCODE對(duì)UIL進(jìn)行加密命名, 還可以用MD5(new Md5FileNameGenerator())加密
            .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) 
            .imageDownloader(new BaseImageDownloader(context)) // default
            .imageDecoder(new BaseImageDecoder()) // default
            .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
            .writeDebugLogs() // 打印debug log
            ImageLoader.getInstance().init(config.build()); //初始化配置错敢,開(kāi)始構(gòu)建

主體有三個(gè)翰灾,分別是UI,緩存模塊和數(shù)據(jù)源(網(wǎng)絡(luò))稚茅。

UI: 請(qǐng)求數(shù)據(jù)纸淮,使用唯一的Key值索引Memory Cache中的Bitmap。
緩存模塊:分兩種亚享,一是內(nèi)存緩存萎馅,通過(guò)緩存搜索,如果能找到Key值對(duì)應(yīng)的Bitmap虹蒋,則返回?cái)?shù)據(jù)糜芳。二是硬盤(pán)存儲(chǔ),使用唯一Key值對(duì)應(yīng)的文件名魄衅,檢索SDCard上的文件峭竣。

默認(rèn)的內(nèi)存緩存實(shí)現(xiàn)是LruMemoryCache,磁盤(pán)緩存是UnlimitedDiscCache晃虫。

  • 先說(shuō)說(shuō) MemoryCache 接口實(shí)現(xiàn)皆撩,底下有很多實(shí)現(xiàn)類
  • 它負(fù)責(zé)定義通用規(guī)則,具體的實(shí)現(xiàn)工作由不同緩存算法的子類去實(shí)現(xiàn)即可哲银。
  • 這是一個(gè)繼承和多態(tài)的體現(xiàn)扛吞。
  • 當(dāng)前分析的就是 LruMemoryCache 類,這也是默認(rèn)的緩存策略
  • 內(nèi)存緩存策略設(shè)計(jì)與實(shí)現(xiàn)
  • 只使用的是強(qiáng)引用緩存
    LruMemoryCache(這個(gè)類就是這個(gè)開(kāi)源框架默認(rèn)的內(nèi)存緩存類荆责,緩存的是bitmap的強(qiáng)引用滥比,下面我會(huì)從源碼上面分析這個(gè)類)

  • 使用強(qiáng)引用和弱引用相結(jié)合的緩存有
    UsingFreqLimitedMemoryCache(如果緩存的圖片總量超過(guò)限定值,先刪除使用頻率最小的bitmap)
    LRULimitedMemoryCache(這個(gè)也是使用的lru算法做院,和LruMemoryCache不同的是盲泛,他緩存的是bitmap的弱引用)
    FIFOLimitedMemoryCache(先進(jìn)先出的緩存策略,當(dāng)超過(guò)設(shè)定值键耕,先刪除最先加入緩存的bitmap)
    LargestLimitedMemoryCache(當(dāng)超過(guò)緩存限定值寺滚,先刪除最大的bitmap對(duì)象)
    LimitedAgeMemoryCache(當(dāng) bitmap加入緩存中的時(shí)間超過(guò)我們?cè)O(shè)定的值,將其刪除)

  • 只使用弱引用緩存
    WeakMemoryCache(這個(gè)類緩存bitmap的總大小沒(méi)有限制屈雄,唯一不足的地方就是不穩(wěn)定村视,緩存的圖片容易被回收掉)

LruMemoryCach

LruMemoryCach這也是默認(rèn)的緩存策略

1、最近最少使用算法酒奶。
2蚁孔、當(dāng)圖片緩存到磁盤(pán)之后奶赔,然后就需要緩存到內(nèi)存中。
3勒虾、需要定義的緩存空間是多大呢纺阔?(默認(rèn)大小為 app 可用空間的 1/8 大小瘸彤。trimToSize 方法可以確保當(dāng)前空間還有剩余修然。)
4、緩存的數(shù)據(jù)結(jié)構(gòu)是什么呢质况?(LinkedHashMap<String,Map>)

LruMemoryCache:一種使用強(qiáng)引用來(lái)保存有數(shù)量限制的Bitmap的cache(在空間有限的情況愕宋,保留最近使用過(guò)的Bitmap)。每次Bitmap被訪問(wèn)時(shí)结榄,它就被移動(dòng)到一個(gè)隊(duì)列的頭部中贝。當(dāng)Bitmap被添加到一個(gè)空間已滿的cache時(shí),在隊(duì)列末尾的Bitmap會(huì)被擠出去并變成適合被GC回收的狀態(tài)臼朗。
注意:這個(gè)cache只使用強(qiáng)引用來(lái)保存Bitmap邻寿。

/** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
    public LruMemoryCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
      //使用LinkedHashMap來(lái)緩存數(shù)據(jù)
        this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
    }

思考:為什么不用HashMap來(lái)緩存數(shù)據(jù)?好了视哑,我們繼續(xù)看源碼

LruMemoryCache.get(...);

/**
     * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
     * of the queue. This returns null if a Bitmap is not cached.
     */
    @Override
    public final Bitmap get(String key) {
     //代碼中除了異常判斷绣否,就是利用synchronized進(jìn)行同步控制。
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        synchronized (this) {
            return map.get(key);
        }
    }
public V get(Object key) {
        LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }

LruMemoryCache保留在空間有限的情況下保留最近使用過(guò)的Bitmap挡毅。
LinkedHashMap中的get()方法不僅返回所匹配的值蒜撮,并且在返回前還會(huì)將所匹配的key對(duì)應(yīng)的entry調(diào)整在列表中的順序(LinkedHashMap使用雙鏈表來(lái)保存數(shù)據(jù),不懂可以去看看Map集合類了解)跪呈,讓它處于列表的最后段磨。當(dāng)然,這種情況必須是在LinkedHashMap中accessOrder==true的情況下才生效的耗绿,反之就是get()方法不會(huì)改變被匹配的key對(duì)應(yīng)的entry在列表中的位置苹支。

@Override
 public V get(Object key) {
          /*
           * This method is overridden to eliminate the need for a polymorphic
           * invocation in superclass at the expense of code duplication.
           */
          if (key == null) {
              HashMapEntry<K, V> e = entryForNullKey;
              if (e == null)
                  return null;
             if (accessOrder)//調(diào)整entry在列表中的位置,其實(shí)就是雙向鏈表的調(diào)整误阻。它判斷accessOrder
                 makeTail((LinkedEntry<K, V>) e);
             return e.value;
         }
 
         // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590).
         int hash = secondaryHash(key);
         HashMapEntry<K, V>[] tab = table;
         for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
                 e != null; e = e.next) {
             K eKey = e.key;
             if (eKey == key || (e.hash == hash && key.equals(eKey))) {
                 if (accessOrder)
                     makeTail((LinkedEntry<K, V>) e);
                 return e.value;
             }
         }
         return null;
     }

結(jié)論

LruMemoryCache緩存的存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)是LinkedHashMap<String,Map>沐序,在LinkedHashMap.get()方法執(zhí)行后,LinkedHashMap中entry的順序會(huì)得到調(diào)整堕绩。

如果圖片過(guò)多策幼,就會(huì)保留最近使用的,其他都要被回收奴紧,怎么才不被剔出呢特姐?

需要定義的緩存空間是多大呢?
1黍氮、默認(rèn)大小為 app 可用空間的 1/8 大小唐含。
2浅浮、trimToSize 方法可以確保當(dāng)前空間還有剩余。

LruMemoryCache中的trimToSize(...)這個(gè)函數(shù)就是用來(lái)限定LruMemoryCache的大小不要超過(guò)用戶限定的大小捷枯,cache的大小由用戶在LruMemoryCache剛開(kāi)始初始化的時(shí)候限定滚秩。那么就看看LruMemoryCache中的put方法。

/** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue. */
    @Override
    public final boolean put(String key, Bitmap value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        synchronized (this) {
            size += sizeOf(key, value);
            //map.put()的返回值如果不為空淮捆,說(shuō)明存在跟key對(duì)應(yīng)的entry郁油,put操作只是更新原有key對(duì)應(yīng)的entry
            Bitmap previous = map.put(key, value);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
        }

        trimToSize(maxSize);
        return true;
    }

當(dāng)Bitmap緩存的大小超過(guò)原來(lái)設(shè)定的maxSize時(shí)應(yīng)該是在trimToSize(...)這個(gè)函數(shù)中做到的。遍歷map攀痊,將多余的項(xiàng)(代碼中對(duì)應(yīng)toEvict)剔除掉桐腌,直到當(dāng)前cache的大小等于或小于限定的大小。

/**
     * Remove the eldest entries until the total of remaining entries is at or below the requested size.
     *
     * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
     */
    private void trimToSize(int maxSize) {
        while (true) {
            String key;
            Bitmap value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= sizeOf(key, value);
            }
        }
    }

磁盤(pán)緩存策略設(shè)計(jì)與實(shí)現(xiàn)

自己實(shí)現(xiàn)磁盤(pán)緩存苟径,要考慮的太多案站,UIL提供了幾種常見(jiàn)的磁盤(pán)緩存策略,當(dāng)然你覺(jué)得都不符合你的要求棘街,你也可以自己去擴(kuò)展的蟆盐。

  • FileCountLimitedDiscCache(可以設(shè)定緩存圖片的個(gè)數(shù),當(dāng)超過(guò)設(shè)定值遭殉,刪除掉最先加入到硬盤(pán)的文件)
  • LimitedAgeDiscCache(設(shè)定文件存活的最長(zhǎng)時(shí)間石挂,當(dāng)超過(guò)這個(gè)值,就刪除該文件)
  • TotalSizeLimitedDiscCache(設(shè)定緩存bitmap的最大值恩沽,當(dāng)超過(guò)這個(gè)值誊稚,刪除最先加入到硬盤(pán)的文件)
  • UnlimitedDiscCache(這個(gè)緩存類沒(méi)有任何的限制)

在UIL中有著比較完整的存儲(chǔ)策略,根據(jù)預(yù)先指定的空間大小罗心,使用頻率(生命周期)里伯,文件個(gè)數(shù)的約束條件,都有著對(duì)應(yīng)的實(shí)現(xiàn)策略渤闷。最基礎(chǔ)的接口DiscCacheAware和抽象類BaseDiscCache

UnlimitedDiscCache解析

UnlimitedDiscCache實(shí)現(xiàn)DiskCache接口疾瓮,是ImageLoaderConfiguration中默認(rèn)的磁盤(pán)緩存處理。用它的時(shí)候飒箭,磁盤(pán)緩存的大小是不受限的狼电。
接下來(lái)看看實(shí)現(xiàn)UnlimitedDiscCache的源代碼,通過(guò)源代碼我們發(fā)現(xiàn)他其實(shí)就是繼承了BaseDiscCache弦蹂,這個(gè)類內(nèi)部沒(méi)有實(shí)現(xiàn)自己獨(dú)特的方法肩碟,也沒(méi)有重寫(xiě)什么,那么我們就直接看BaseDiscCache這個(gè)類凸椿。BaseDiscCache繼承了DiscCache削祈。在分析這個(gè)類之前,我們先想想自己實(shí)現(xiàn)一個(gè)磁盤(pán)緩存需要做多少麻煩的事情:

  1. 圖片的命名會(huì)不會(huì)重。你沒(méi)有辦法知道用戶下載的圖片原始的文件名是怎么樣的髓抑,因此很可能因?yàn)槲募孛麑⒂杏玫膱D片給覆蓋掉了咙崎。
  2. 當(dāng)應(yīng)用卡頓或網(wǎng)絡(luò)延遲的時(shí)候,同一張圖片反復(fù)被下載吨拍。
  3. 處理圖片寫(xiě)入磁盤(pán)可能遇到的延遲和同步問(wèn)題褪猛。

直接上源碼

/**
     * @param cacheDir          Directory for file caching
     * @param reserveCacheDir   null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
     * @param fileNameGenerator {@linkplain com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator
     *                          Name generator} for cached files
     */
    public BaseDiskCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
        if (cacheDir == null) {
            throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL);
        }
        if (fileNameGenerator == null) {
            throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL);
        }

        this.cacheDir = cacheDir;
        this.reserveCacheDir = reserveCacheDir;
        this.fileNameGenerator = fileNameGenerator;
    }

我們看一下BaseDiscCache的構(gòu)造函數(shù):
cacheDir: 文件緩存目錄
reserveCacheDir: 備用的文件緩存目錄,可以為null羹饰。它只有當(dāng)cacheDir不能用的時(shí)候才有用伊滋。
fileNameGenerator: 文件名生成器。為緩存的文件生成文件名严里。

當(dāng)看到fileNameGenerator新啼,有點(diǎn)疑慮追城,我們并沒(méi)有設(shè)置文件名吧材搿?查閱代碼發(fā)現(xiàn)是默認(rèn)生成DefaultConfigurationFactory.createFileNameGenerator())座柱。
代碼如下:

/**
     * @param cacheDir        Directory for file caching
     * @param reserveCacheDir null-ok; Reserve directory for file caching. It's used when the primary directory isn't available.
     */
    public BaseDiskCache(File cacheDir, File reserveCacheDir) {
        this(cacheDir, reserveCacheDir, DefaultConfigurationFactory.createFileNameGenerator());
    }
/** Creates {@linkplain HashCodeFileNameGenerator default implementation} of FileNameGenerator */
    public static FileNameGenerator createFileNameGenerator() {
        return new HashCodeFileNameGenerator();
    }
/**
 * Names image file as image URI {@linkplain String#hashCode() hashcode}
 *
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
 * @since 1.3.1
 */
public class HashCodeFileNameGenerator implements FileNameGenerator {
    @Override
    public String generate(String imageUri) {
        return String.valueOf(imageUri.hashCode());
    }
}

UIL中有3種文件命名策略迷帜,默認(rèn)的文件命名策略DefaultConfigurationFactory.createFileNameGenerator()。它是一個(gè)HashCodeFileNameGenerator色洞。用String.hashCode()進(jìn)行文件名的生成戏锹,這樣也不會(huì)生成重復(fù)的文件名,就那么簡(jiǎn)單火诸。

接著看看是如何存儲(chǔ)數(shù)據(jù)的
直接看源碼

@Override
    public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
        //主要用于生成一個(gè)指向緩存目錄中的文件锦针,在這個(gè)函數(shù)里面調(diào)用了剛剛介紹過(guò)的fileNameGenerator來(lái)生成文件名。
        File imageFile = getFile(imageUri);
        //它是用來(lái)寫(xiě)入bitmap的臨時(shí)文件置蜀,然后就把這個(gè)文件給刪除了
        File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
        boolean loaded = false;
        try {
            OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
            try {
                loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);
            } finally {
                IoUtils.closeSilently(os);
            }
        } finally {
            if (loaded && !tmpFile.renameTo(imageFile)) {
                loaded = false;
            }
            if (!loaded) {
                tmpFile.delete();
            }
        }
        return loaded;
    }

UIL加載圖片的一般流程是先判斷內(nèi)存中是否有對(duì)應(yīng)的Bitmap奈搜,再判斷磁盤(pán)(disk)中是否有,如果沒(méi)有就從網(wǎng)絡(luò)中加載盯荤。最后根據(jù)原先在UIL中的配置判斷是否需要緩存Bitmap到內(nèi)存或磁盤(pán)中馋吗。也就是說(shuō),當(dāng)需要調(diào)用BaseDiscCache.save(...)之前秋秤,其實(shí)已經(jīng)判斷過(guò)這個(gè)文件不在磁盤(pán)中宏粤。

/** Returns file object (not null) for incoming image URI. File object can reference to non-existing file. */
    protected File getFile(String imageUri) {
       //利用fileNameGenerator生成一個(gè)唯一的文件名
        String fileName = fileNameGenerator.generate(imageUri);
        File dir = cacheDir;
        //當(dāng)cacheDir不可用的時(shí)候,就是用reserveCachedir作為緩存目錄了灼卢。
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {
            if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
                dir = reserveCacheDir;
            }
        }
        //最后返回一個(gè)指向文件的對(duì)象绍哎,但是要注意當(dāng)File類型的對(duì)象指向的文件不存在時(shí),file會(huì)為null鞋真,而不是報(bào)錯(cuò)崇堰。
        return new File(dir, fileName);
    }

總結(jié)
內(nèi)存緩存其實(shí)就是利用Map接口的對(duì)象在內(nèi)存中進(jìn)行緩存,可能有不同的存儲(chǔ)機(jī)制灿巧。磁盤(pán)緩存其實(shí)就是將文件寫(xiě)入磁盤(pán)赶袄。這樣就達(dá)到緩存目的揽涮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市饿肺,隨后出現(xiàn)的幾起案子蒋困,更是在濱河造成了極大的恐慌,老刑警劉巖敬辣,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雪标,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡溉跃,警方通過(guò)查閱死者的電腦和手機(jī)村刨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撰茎,“玉大人嵌牺,你說(shuō)我怎么就攤上這事×浜” “怎么了逆粹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)炫惩。 經(jīng)常有香客問(wèn)我僻弹,道長(zhǎng),這世上最難降的妖魔是什么他嚷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任蹋绽,我火速辦了婚禮,結(jié)果婚禮上筋蓖,老公的妹妹穿的比我還像新娘卸耘。我一直安慰自己,他們只是感情好扭勉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布鹊奖。 她就那樣靜靜地躺著,像睡著了一般涂炎。 火紅的嫁衣襯著肌膚如雪忠聚。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天唱捣,我揣著相機(jī)與錄音两蟀,去河邊找鬼。 笑死震缭,一個(gè)胖子當(dāng)著我的面吹牛赂毯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼党涕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼烦感!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起膛堤,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤手趣,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后肥荔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體绿渣,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年燕耿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了行冰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸵膏。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钦勘,死狀恐怖氧卧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堵第,我是刑警寧澤吧凉,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布隧出,位于F島的核電站踏志,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胀瞪。R本人自食惡果不足惜针余,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凄诞。 院中可真熱鬧圆雁,春花似錦、人聲如沸帆谍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)汛蝙。三九已至烈涮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窖剑,已是汗流浹背坚洽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留西土,地道東北人讶舰。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親跳昼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子般甲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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