Glide 源碼分析解讀-緩存模塊-基于最新版Glide 4.9.0

緩存模塊


我在分析 Glide 源碼前將 Glide 的項(xiàng)目 clone 到了本地第煮,閱讀時(shí)添加了很多注釋以及自己的理解等等害晦,現(xiàn)在已經(jīng)推到了 Github 上稻轨,有興趣的同學(xué)可以看看:
https://github.com/0xZhangKe/Glide-note

緩存模塊涉及到的東西比較多,比較重要李丰,所以需要單獨(dú)用一章節(jié)來(lái)講。

關(guān)于緩存的獲取、數(shù)據(jù)加載相關(guān)的邏輯在 Engine#load 方法中。
先來(lái)看看緩存流程床佳,流程如下圖:

cache_process

全部的緩存流程大致如上圖所示。

Glide 實(shí)例化時(shí)會(huì)實(shí)例化三個(gè)緩存相關(guān)的類(lèi)以及一個(gè)計(jì)算緩存大小的類(lèi):

//根據(jù)當(dāng)前機(jī)器參數(shù)計(jì)算需要設(shè)置的緩存大小
MemorySizeCalculator calculator = new MemorySizeCalculator(context);
//創(chuàng)建 Bitmap 池
if (bitmapPool == null) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        int size = calculator.getBitmapPoolSize();
        bitmapPool = new LruBitmapPool(size);
    } else {
        bitmapPool = new BitmapPoolAdapter();
    }
}
//創(chuàng)建內(nèi)存緩存
if (memoryCache == null) {
    memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
}
//創(chuàng)建磁盤(pán)緩存
if (diskCacheFactory == null) {
    diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}

除此之外 Engine 中還有一個(gè) ActiveResources 作為第一級(jí)緩存浪感。下面分別來(lái)介紹一下昔头。

ActiveResources


ActiveResources 是第一級(jí)緩存,表示當(dāng)前正在活動(dòng)中的資源影兽。
類(lèi)路徑:

com.bumptech.glide.load.engine.ActiveResources

Engine#load 方法中構(gòu)建好 Key 之后第一件事就是去這個(gè)緩存中獲取資源揭斧,獲取到則直接返回,獲取不到才繼續(xù)從其他緩存中尋找峻堰。

當(dāng)資源加載成功讹开,或者通過(guò)緩存中命中資源后都會(huì)將其放入 ActivityResources 中,資源被釋放時(shí)移除出 ActivityResources 捐名。

由于其中的生命周期較短旦万,所以沒(méi)有大小限制

ActiveResources 中通過(guò)一個(gè) Map 來(lái)存儲(chǔ)數(shù)據(jù)镶蹋,數(shù)據(jù)保存在一個(gè)虛引用(WeakReference)中成艘。

剛剛說(shuō)的 activeResource 使用一個(gè) Map<Key, WeakReference<EngineResource<?>>> 來(lái)存儲(chǔ)的赏半,此外還有一個(gè)引用隊(duì)列:

ReferenceQueue<EngineResource<?>> resourceReferenceQueue;

每當(dāng)向 activeResource 中添加一個(gè) WeakReference 對(duì)象時(shí)都會(huì)將 resourceReferenceQueue 和這個(gè) WeakReference 關(guān)聯(lián)起來(lái),用來(lái)跟蹤這個(gè) WeakReference 的 gc狰腌,一旦這個(gè)弱引用被 gc 掉除破,就會(huì)將它從 activeResource 中移除,ReferenceQueue 的具體作用可以自行谷歌琼腔,大概就是用來(lái)跟蹤弱引用(或者軟引用瑰枫、虛引用)是否被 gc 的。

那么 ReferenceQueue 具體是在何時(shí)去判斷 WeakReference 是否被 gc 了呢丹莲,Handler 機(jī)制大家應(yīng)該都知道光坝,但不知道大家有沒(méi)有用過(guò) MessageQueue.IdleHandler 這個(gè)東東,可以調(diào)用 MessageQueue#addIdleHandler 添加一個(gè) MessageQueue.IdleHandler 對(duì)象甥材,Handler 會(huì)在線程空閑時(shí)調(diào)用這個(gè)方法盯另。resourceReferenceQueue 在創(chuàng)建時(shí)會(huì)創(chuàng)建一個(gè) Engine#RefQueueIdleHandler 對(duì)象并將其添加到當(dāng)前線程的 MessageQueue 中,ReferenceQueue 會(huì)在 IdleHandler 回調(diào)的方法中去判斷 activeResource 中的 WeakReference 是不是被 gc 了洲赵,如果是鸳惯,則將引用從 activeResource 中移除,代碼如下:

//MessageQueue 中的消息暫時(shí)處理完回調(diào)
@Override
public boolean queueIdle() {
    ResourceWeakReference ref = (ResourceWeakReference) queue.poll();
    if (ref != null) {
        activeResources.remove(ref.key);
    }
    //返回 true叠萍,表示下次處理完仍然繼續(xù)回調(diào)
    return true;
}

MemorySizeCalculator


這個(gè)類(lèi)是用來(lái)計(jì)算 BitmapPool 芝发、ArrayPool 以及 MemoryCache 大小的。
計(jì)算方式如下:

//默認(rèn)為 4MB苛谷,如果是低內(nèi)存設(shè)備則在此基礎(chǔ)上除以二
arrayPoolSize =
        isLowMemoryDevice(builder.activityManager)
                ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
                : builder.arrayPoolSizeBytes;
//其中會(huì)先獲取當(dāng)前進(jìn)程可使用內(nèi)存大小辅鲸,
//然后通過(guò)判斷是否是否為低內(nèi)存設(shè)備乘以相應(yīng)的系數(shù),
//普通設(shè)備是乘以 0.4腹殿,低內(nèi)存為 0.33独悴,這樣得到的是 Glide 可使用的最大內(nèi)存閾值 maxSize
int maxSize =
        getMaxSize(
                builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);
?
int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
//計(jì)算一張格式為 ARGB_8888 ,大小為屏幕大小的圖片的占用內(nèi)存大小
//BYTES_PER_ARGB_8888_PIXEL 值為 4
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
?
int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
?
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
//去掉 ArrayPool 占用的內(nèi)存后還剩余的內(nèi)存
int availableSize = maxSize - arrayPoolSize;
?
if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
    //未超出內(nèi)存限制
    memoryCacheSize = targetMemoryCacheSize;
    bitmapPoolSize = targetBitmapPoolSize;
} else {
    //超出內(nèi)存限制
    float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
    memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
    bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
}

直接看上面的注釋即可锣尉。

BitmapPool


Bitmap 是用來(lái)復(fù)用 Bitmap 從而避免重復(fù)創(chuàng)建 Bitmap 而帶來(lái)的內(nèi)存浪費(fèi)刻炒,Glide 通過(guò) SDK 版本不同創(chuàng)建不同的 BitmapPool 實(shí)例,版本低于 Build.VERSION_CODES.HONEYCOMB(11) 實(shí)例為 BitmapPoolAdapter悟耘,其中的方法體幾乎都是空的落蝙,也就是是個(gè)實(shí)例不做任何緩存。
否則實(shí)例為 LruBitmapPool暂幼,先來(lái)看這個(gè)類(lèi)筏勒。

LruBitmapPool


LruBitmapPool 中沒(méi)有做太多的事,主要任務(wù)都交給了 LruPoolStrategy旺嬉,這里只是做一些緩存大小管理管行、封裝、日志記錄等等操作邪媳。

每次調(diào)用 put 緩存數(shù)據(jù)時(shí)都會(huì)調(diào)用 trimToSize 方法判斷已緩存內(nèi)容是否大于設(shè)定的最大內(nèi)存捐顷,如果大于則使用 LruPoolStrategy#removeLast 方法逐步移除荡陷,直到內(nèi)存小于設(shè)定的最大內(nèi)存為止。

LruPoolStrategy 有兩個(gè)實(shí)現(xiàn)類(lèi):SizeConfigStrategy 以及 AttributeStrategy迅涮,根據(jù)系統(tǒng)版本創(chuàng)建不同的實(shí)例废赞,這兩個(gè)差異不大,KITKAT 之后使用的都是 SizeConfigStrategy叮姑,這個(gè)比較重要唉地。

SizeConfigStrategy


SizeConfigStrategy 顧名思義,是通過(guò) Bitmap 的 size 與 Config 來(lái)當(dāng)做 key 緩存 Bitmap传透,Key 也會(huì)通過(guò) KeyPool 來(lái)緩存在一個(gè)隊(duì)列(Queue)中耘沼。

與 AttributeStrategy 相同的是,其中都使用 Glide 內(nèi)部自定義的數(shù)據(jù)結(jié)構(gòu):GroupedLinkedMap 來(lái)存儲(chǔ) Bitmap朱盐。

當(dāng)調(diào)用 put 方法緩存一個(gè) Bitmap 時(shí)會(huì)先通過(guò) Bitmap 的大小以及 Bitmap.Config 創(chuàng)建(從 KeyPool 中獲热亨汀)Key,然后將這個(gè) Key 與 Bitmap 按照鍵值對(duì)的方式存入 GroupedLinkedMap 中兵琳。

此外其中還包含一個(gè) sortedSizes狂秘,這是一個(gè) HashMap,Key 對(duì)應(yīng) put 進(jìn)來(lái)的 Bitmap.Config躯肌,value 對(duì)應(yīng)一個(gè) TreeMap赃绊,TreeMap 中記錄著每一個(gè) size 的 Bitmap 在當(dāng)前緩存中的個(gè)數(shù),即 put 時(shí)加一羡榴,get 時(shí)減一。

TreeMap 是有序的數(shù)據(jù)結(jié)構(gòu)运敢,當(dāng)需要通過(guò) Bitmap 的 size 與 Config 從緩存中獲取一個(gè) Biamp 時(shí)未必會(huì)一定要獲取到 size 完全相同的 Bitmap校仑,由于 TreeMap 的特性,調(diào)用其 ceilingKey 可以獲取到一個(gè)相等或大于當(dāng)前 size 的一個(gè)最小值传惠,用這個(gè) Key 去獲取 Bitmap迄沫,然后重置一下大小即可。

重點(diǎn)看一下 GroupedLinkedMap卦方,這是 Glide 為了 實(shí)現(xiàn) LRU 算法自定義的一個(gè)數(shù)據(jù)結(jié)構(gòu)羊瘩,看名字是已分組的鏈表 Map?看一下下面的圖就明白了:

GroupedLinkedMap

其中包含三種數(shù)據(jù)結(jié)構(gòu):哈希表(HashMap)盼砍、循環(huán)鏈表以及列表(ArrayList)靶累。
這個(gè)結(jié)構(gòu)其實(shí)類(lèi)似 Java 里提供的 LinkedHashMap 類(lèi)岭妖。

循環(huán)鏈表是通過(guò)內(nèi)部類(lèi) GroupedLinkedMap$LinkedEntry 實(shí)現(xiàn)的,其中除了定義了鏈表結(jié)構(gòu)需要的上下兩個(gè)節(jié)點(diǎn)信息之外還包含著一個(gè) Key 與一個(gè) Values,定義如下:

private static class LinkedEntry<K, V> {
    private final K key;
    private List<V> values;
    LinkedEntry<K, V> next;
    LinkedEntry<K, V> prev;
    
    ...
}

其實(shí)就是將 HashMap 的 Values 使用鏈表串了起來(lái)勃教,每個(gè) Value 中又存了個(gè) List

調(diào)用 put 方法時(shí)會(huì)先根據(jù) Key 去這個(gè) Map 中獲取 LinkedEntry熙尉,獲取不到則創(chuàng)建一個(gè),并且加入到鏈表的尾部臀晃,然后將 value (也就是 Bitmap)存入 LinkedEntry 中的 List 中。

所以這里說(shuō)的分組指的是通過(guò) Key 來(lái)對(duì) Bitmap 進(jìn)行分組介劫,對(duì)于同一個(gè) Key(size 與 config 都相同)的 Bitmap 都會(huì)存入同一個(gè) LinkedEntry 中徽惋。

調(diào)用 get 方法獲取 Bitmap 時(shí)會(huì)先通過(guò) Key 去 keyToEntry 中獲取 LinkedEntry 對(duì)象,獲取不到則創(chuàng)建一個(gè)座韵,然后將其加入到鏈表頭部险绘,此時(shí)已經(jīng)有了 LinkedEntry 對(duì)象,調(diào)用 LinkedEntry#removeLast 方法返回并刪除 List 中的最后一個(gè)元素回右。

通過(guò)上面兩步可以看到之所以使用鏈表是為了支持 LRU 算法隆圆,最近使用的 Bitmap 都會(huì)移動(dòng)到鏈表的前端,使用次數(shù)越少就越靠后翔烁,當(dāng)調(diào)用 removeLast 方法時(shí)就直接調(diào)用鏈表最后一個(gè)元素的 removeLast 方法移除元素渺氧。

好了 BitmapPool 大概就這么多內(nèi)容,總結(jié)一下:

  1. BitmapPool 大小通過(guò) MemorySizeCalculator 設(shè)置蹬屹;
  2. 使用 LRU 算法維護(hù) BitmapPool 侣背;
  3. Glide 會(huì)根據(jù) Bitmap 的大小與 Config 生成一個(gè) Key;
  4. Key 也有自己對(duì)應(yīng)的對(duì)象池慨默,使用 Queue 實(shí)現(xiàn)贩耐;
  5. 數(shù)據(jù)最終存儲(chǔ)在 GroupedLinkedMap 中;
  6. GroupedLinkedMap 使用哈希表厦取、循環(huán)鏈表潮太、List 來(lái)存儲(chǔ)數(shù)據(jù)。

MemoryCache


相比較而言內(nèi)存緩存就簡(jiǎn)單多了虾攻,如果從上面說(shuō)的 ActiveResources 中沒(méi)獲取到資源則開(kāi)始從這里尋找铡买。
內(nèi)存緩存同樣使用 LRU 算法,實(shí)現(xiàn)類(lèi)為 LruResourceCache霎箍,這個(gè)類(lèi)沒(méi)幾行代碼奇钞,繼承了 LruCache ,所以著重看一下 LruCache 好了漂坏。

其實(shí) Java 集合里面提供了一個(gè)很好的用來(lái)實(shí)現(xiàn) LRU 算法的數(shù)據(jù)結(jié)構(gòu)景埃,即上面提到過(guò)的 LinkedHashMap。其基于 HashMap 實(shí)現(xiàn)顶别,同時(shí)又將 HashMap 中的 Entity 串成了一個(gè)雙向鏈表谷徙。
LruCache 中就是使用這個(gè)集合來(lái)緩存數(shù)據(jù),其中代碼量也不多筋夏,主要就是在 LinkedHashMap 的基礎(chǔ)上又提供了對(duì)內(nèi)存的管理的幾個(gè)操作蒂胞。

特別地,LruResourceCache 中提供了一個(gè) ResourceRemovedListener 接口条篷,當(dāng)有資源從 MemoryCache 中被移除時(shí)會(huì)回調(diào)其中的方法骗随,Engine 中接收到這個(gè)消息后就會(huì)進(jìn)行 Bitmap 的回收操作蛤织。

磁盤(pán)緩存


緩存路徑默認(rèn)為 Context#getCacheDir() 下面的 image_manager_disk_cache 文件夾,默認(rèn)緩存大小為 250MB鸿染。

磁盤(pán)緩存實(shí)現(xiàn)類(lèi)由 InternalCacheDiskCacheFactory 創(chuàng)建指蚜,最終會(huì)通過(guò)緩存路徑及緩存文件夾最大值創(chuàng)建一個(gè) DiskLruCacheWrapper 對(duì)象。

DiskLruCacheWrapper 實(shí)現(xiàn)了 DiskCache 接口涨椒,接口主要的代碼如下:

File get(Key key);
void put(Key key, Writer writer);
void delete(Key key);
void clear();

可以看到其中提供了作為一個(gè)緩存類(lèi)必須的幾個(gè)方法摊鸡,并且文件以 Key 的形式操作。

SafeKeyGenerator 類(lèi)用來(lái)將 Key 對(duì)象轉(zhuǎn)換為字符串蚕冬,Key 不同的實(shí)現(xiàn)類(lèi)生成 Key 的方式也不同免猾,一般來(lái)說(shuō)會(huì)通過(guò)圖片寬高、加密解碼器囤热、引擎等等生成一個(gè) byte[] 然后再轉(zhuǎn)為字符串猎提,以此來(lái)保證圖片資源的唯一性

另外旁蔼,在向磁盤(pán)寫(xiě)入文件時(shí)(put 方法)會(huì)使用重入鎖來(lái)同步代碼锨苏,也就是 DiskCacheWriteLocker 類(lèi),其中主要是對(duì) ReentrantLock 的包裝棺聊。

DiskLruCacheWrapper 顧名思義也是一個(gè)包裝類(lèi)伞租,包裝的是 DiskLruCache,那再來(lái)看看這個(gè)類(lèi)限佩。

DiskLruCache


這里考慮一個(gè)問(wèn)題葵诈,磁盤(pán)緩存同樣使用的是 LRU 算法,但文件是存在磁盤(pán)中的祟同,如何在 APP 啟動(dòng)之后準(zhǔn)確的按照使用次數(shù)排序讀取緩存文件呢驯击?

Glide 是使用一個(gè)日志清單文件來(lái)保存這種順序,DiskLruCache 在 APP 第一次安裝時(shí)會(huì)在緩存文件夾下創(chuàng)建一個(gè) journal 日志文件來(lái)記錄圖片的添加耐亏、刪除、讀取等等操作沪斟,后面每次打開(kāi) APP 都會(huì)讀取這個(gè)文件广辰,把其中記錄下來(lái)的緩存文件名讀取到 LinkedHashMap 中,后面每次對(duì)圖片的操作不僅是操作這個(gè) LinkedHashMap 還要記錄在 journal 文件中.
journal 文件內(nèi)容如下圖:

journal 文件內(nèi)容

開(kāi)頭的 libcore.io.DiskLruCache 是魔數(shù)主之,用來(lái)標(biāo)識(shí)文件择吊,后面的三個(gè) 1 是版本號(hào) valueCount 等等,再往下就是圖片的操作日志了槽奕。

DIRTY几睛、CLEAN 代表操作類(lèi)型,除了這兩個(gè)還有 REMOVE 以及 READ粤攒,緊接著的一長(zhǎng)串字符串是文件的 Key所森,由上文提到的 SafeKeyGenerator 類(lèi)生成囱持,是由圖片的寬、高焕济、加密解碼器等等生成的 SHA-256 散列碼 后面的數(shù)字是圖片大小纷妆。

根據(jù)這個(gè)字符串就可以在同目錄下找到對(duì)應(yīng)的圖片緩存文件,那么打開(kāi)緩存文件夾即可看到上面日志中記錄的文件:


緩存文件列表

可以看到日志文件中記錄的緩存文件就在這個(gè)文件夾下面晴弃。

由于涉及到磁盤(pán)緩存的外部排序問(wèn)題掩幢,所以相對(duì)而言磁盤(pán)緩存比較復(fù)雜。

那么 Glide 的緩存模塊至此就結(jié)束了上鞠,主要是 BitmapPool 中的數(shù)據(jù)結(jié)構(gòu)以及磁盤(pán)緩存比較復(fù)雜际邻,其他的倒也不是很復(fù)雜。

關(guān)于 Glide 其它模塊的源碼解析可以看我的上一篇博客
http://www.reibang.com/p/9bb50924d42a

另外芍阎,我在分析 Glide 源碼前將 Glide 的項(xiàng)目 clone 到了本地世曾,閱讀時(shí)添加了很多注釋以及自己的理解等等,現(xiàn)在已經(jīng)推到了 Github 上能曾,有興趣的同學(xué)可以看看:
https://github.com/0xZhangKe/Glide-note

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末度硝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子寿冕,更是在濱河造成了極大的恐慌蕊程,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驼唱,死亡現(xiàn)場(chǎng)離奇詭異藻茂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)玫恳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)辨赐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人京办,你說(shuō)我怎么就攤上這事掀序。” “怎么了惭婿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵不恭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我财饥,道長(zhǎng)换吧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任钥星,我火速辦了婚禮沾瓦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己贯莺,他們只是感情好风喇,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著乖篷,像睡著了一般响驴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撕蔼,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天豁鲤,我揣著相機(jī)與錄音,去河邊找鬼鲸沮。 笑死琳骡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的讼溺。 我是一名探鬼主播楣号,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼怒坯!你這毒婦竟也來(lái)了炫狱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤剔猿,失蹤者是張志新(化名)和其女友劉穎视译,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體归敬,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酷含,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汪茧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椅亚。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖舱污,靈堂內(nèi)的尸體忽然破棺而出呀舔,到底是詐尸還是另有隱情,我是刑警寧澤扩灯,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布别威,位于F島的核電站,受9級(jí)特大地震影響驴剔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粥庄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一丧失、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惜互,春花似錦布讹、人聲如沸琳拭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)白嘁。三九已至,卻和暖如春膘流,著一層夾襖步出監(jiān)牢的瞬間絮缅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工呼股, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耕魄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓彭谁,卻偏偏與公主長(zhǎng)得像吸奴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缠局,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345