〔兩行哥〕提綱挈領赶诊,帶你梳理Glide主要源碼邏輯(二)

上篇文章主要分析Glide的處理流程以及with()方法的內(nèi)部邏輯(參閱:〔兩行哥〕提綱挈領吱型,帶你梳理Glide主要源碼邏輯(一))逸贾,本篇主要分析load()方法,同時為大家介紹Bitmap優(yōu)化和LruCache算法相關理論,為最后一個重頭戲into()方法做鋪墊铝侵。
Glide.with()方法返回值類型為RequestManger灼伤,那么我們繼續(xù)分析load()方法也主要集中在RequestManger類中。

load()方法主要是對Glide內(nèi)部的Model進行封裝與處理(什么是Model咪鲜?請參閱上篇文章)狐赡,最終形成圖片加載請求。

一疟丙、load()源碼邏輯

在RequestManger類中提供了多種load()方法的重載颖侄,包括load(String string)、load(Uri uri)享郊、load(Integer resId)览祖、load(File file)等,分別適用于加載網(wǎng)絡圖片地址拂蝎,加載圖片Uri穴墅,加載圖片resId,加載圖片文件等温自,我們以加載網(wǎng)絡圖片地址為例進行分析:

RequestManger.java
    ......省略
    public DrawableTypeRequest<String> load(String string) {
        return (DrawableTypeRequest<String>) fromString().load(string);
    }
    ......省略
    public DrawableTypeRequest<String> fromString() {
        return loadGeneric(String.class);
    }
    ......省略
    private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader = Glide.buildFileDescriptorModelLoader(modelClass, context);
        if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
            throw new IllegalArgumentException("......");
        }
        return optionsApplier.apply(new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,glide, requestTracker, lifecycle, optionsApplier));
    }
    ......省略

先看load()方法的返回值類型:DrawableTypeRequest<ModelType>玄货,可以理解為Glide中加載圖片的請求對象。DrawableTypeRequest<ModelType>繼承了DrawableRequestBuilder<ModelType>悼泌,而 DrawableRequestBuilder<ModelType>又繼承了GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable>松捉。根據(jù)類名判斷,GenericRequestBuilder類使用了構建者模式馆里,追蹤一下源碼:

GenericRequestBuilder.java
    protected final Class<ModelType> modelClass;
    protected final Context context;
    protected final Glide glide;
    protected final Class<TranscodeType> transcodeClass;
    protected final RequestTracker requestTracker;//請求追蹤器
    protected final Lifecycle lifecycle;
    private ChildLoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider;
    private ModelType model;
    private Key signature = EmptySignature.obtain();
    // model may occasionally be null, so to enforce that load() was called, set a boolean rather than relying on model not to be null.
    private boolean isModelSet;
    private int placeholderId;//占位圖ResId
    private int errorId;//加載失敗圖ResId
    private RequestListener<? super ModelType, TranscodeType> requestListener;//請求監(jiān)聽
    private Float thumbSizeMultiplier;
    private GenericRequestBuilder<?, ?, ?, TranscodeType> thumbnailRequestBuilder;
    private Float sizeMultiplier = 1f;//尺寸縮放比例
    private Drawable placeholderDrawable;//占位圖Drawable
    private Drawable errorPlaceholder;//加載失敗圖Drawable
    private Priority priority = null;
    private boolean isCacheable = true;
    private GlideAnimationFactory<TranscodeType> animationFactory = NoAnimation.getFactory();
    private int overrideHeight = -1;//覆寫高度
    private int overrideWidth = -1;//覆寫寬度
    private DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.RESULT;//磁盤緩存策略
    private Transformation<ResourceType> transformation = UnitTransformation.get();
    private boolean isTransformationSet;
    private boolean isThumbnailBuilt;
    private Drawable fallbackDrawable;
    private int fallbackResource;

上述源碼截取了GenericRequestBuilder類所有的成員變量隘世。可以發(fā)現(xiàn)Glide通過構建者模式配置的所有參數(shù)都在這里(什么是構建者模式鸠踪?請讀者自行查閱學習)丙者,load()方法最終返回了加載圖片的請求對象(DrawableTypeRequest<ModelType>實例)。
接著看load()方法體內(nèi)的fromString()方法营密。fromString()方法內(nèi)部調用了loadGeneric()方法械媒。在loadGeneric()方法內(nèi)部,我們看到了熟悉的名字:ModelLoader评汰。在上一篇中纷捞,我們已經(jīng)介紹過,將Model轉化為Data的角色就是ModelLoader被去。這里一共創(chuàng)建了兩個ModelLoader主儡,一個是輸入流ModelLoader,一個是文檔描述符ModelLoader惨缆。方法最終返回了optionsApplier.apply(new DrawableTypeRequest<T>(...))糜值。追蹤看看optionsApplier.apply()做了哪些操作丰捷,還是在RequestManger類中:

RequestManger.java
    private final OptionsApplier optionsApplier;//用戶自定義的Glide配置套用者
    private DefaultOptions options;//用戶自定義的Glide配置
    ......省略
    public interface DefaultOptions {
        /**
         * Allows the implementor to apply some options to the given request.
         *
         * @param requestBuilder The request builder being used to construct the load.
         * @param <T> The type of the model.
         */
        <T> void apply(GenericRequestBuilder<T, ?, ?, ?> requestBuilder);
    }
    ......省略
    class OptionsApplier {

        public <A, X extends GenericRequestBuilder<A, ?, ?, ?>> X apply(X builder) {
            if (options != null) {
                options.apply(builder);
            }
            return builder;
        }
    }
    ......省略

OptionsApplier(optionsApplier)為RequestManger的內(nèi)部類,只有一個apply(X builder)方法臀玄。在apply(X builder)中瓢阴,對options進行了非空判斷,如果不為空健无,就調用options的apply()方法荣恐。如源碼中的注釋說明,options為RequestManger類的成員變量(用戶自定義的Glide配置)累贤,如果用戶沒有傳入options叠穆,則默認值為null。apply(X builder)方法最終將參數(shù)builder進行了返回臼膏,結合上文來看硼被,builder即 DrawableTypeRequest<T>的實例。
綜上渗磅,loadGeneric()方法最終創(chuàng)建了一個DrawableTypeRequest<T>對象并進行了返回嚷硫,在創(chuàng)建DrawableTypeRequest<T>對象的構造方法中,傳入了之前所述的兩個ModelLoader以及requestTracker(請求追蹤器)等始鱼。

注:RequestTracker是Glide中一個核心類仔掸,將在下一篇into()方法中著重介紹。

二医清、Bitmap優(yōu)化

(一)Bitmap的OOM

Bitmap占用內(nèi)存大小 = 同時加載的Bitmap數(shù)量 * 每個Bitmap圖片的寬度px * 每個Bitmap圖片的高度px * 每個像素占用的內(nèi)存起暮。而每個像素占有多大的內(nèi)存呢?這取決于此像素的類別及是否采用了壓縮技術会烙。
如果是非黑即白的二值圖像负懦,不壓縮的情況下一個像素只需要1個bit。
如果是256種(2的8次方)狀態(tài)的灰度圖像柏腻,不壓縮的情況下一個像素需要8bit(1Byte纸厉,256種狀態(tài))。
如果用256種(2的8次方)狀態(tài)標識屏幕上某種顏色的灰度五嫂,而屏幕采用三基色紅綠藍(RGB)残腌,不壓縮的情況下一個像素需要占用24bit(3Byte),這個就是常說的24位真彩色贫导。
還有各種其他的存儲方式,例如15bit蟆盹、16bit孩灯、32bit等。如果考慮到壓縮逾滥,有損壓縮或無損壓縮峰档,具體采用的壓縮算法及壓縮參數(shù)設置都會影響一個像素占用的存儲空間败匹。
例如,如果在頁面顯示一張1920 * 1080的圖片讥巡,采用Android內(nèi)置的ARGB_8888壓縮掀亩,占用的內(nèi)存大約為8MB左右。
而每個Android應用的VM堆內(nèi)存上限是通過dalvik.vm.heapgrowthlimit設置(參閱:Android Dalvik Heap 淺析)欢顷,如果同一個頁面展示的圖片長寬過大或數(shù)量過多槽棍,占用的內(nèi)存超過了此上限值,就會導致OOM抬驴。

注:1GB = 1024MB炼七;1MB = 1024KB;1KB = 1024Byte布持;1Byte = 8bit豌拙。
(二)Bitmap的優(yōu)化策略

1.選擇不同的圖片壓縮策略,比如使用Bitmap.Config.RGB_565代替Bitmap.Config.ARGB_8888题暖,同時對圖片進行壓縮等按傅;

    /**
     * @param bitmap  源Bitmap
     * @param maxSize 目標Bitmap最大值(KB)
     * @return
     */
    private Bitmap zipBitmap(Bitmap bitmap, int maxSize) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(CompressFormat.JPEG, 100, baos);
        int options = 100;
        while ((baos.toByteArray().length / 1024 > maxSize)) {
            baos.reset();
            bitmap.compress(CompressFormat.JPEG, options, baos);
            options -= 5;
        }
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        return BitmapFactory.decodeStream(bais);
    }

2.將圖片按比例壓縮尺寸后再展示;

    /**
     * @param bitmap    源Bitmap
     * @param maxWidth  目標Bitmap最大寬
     * @param maxHeight 目標Bitmap最大高
     * @return
     */
    private Bitmap zipBitMap(Bitmap bitmap, int maxWidth, int maxHeight) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(CompressFormat.JPEG, 100, baos);
        Options options = new Options();
        options.inJustDecodeBounds = true;
        float width = options.outWidth * 1.0F;
        float height = options.outHeight * 1.0F;
        int size = 1;
        if (width > maxWidth || height > maxHeight) {
            int widthRatio = Math.round(width / maxWidth);
            int heightRatio = Math.round(height / maxHeight);
            size = widthRatio > heightRatio ? widthRatio : heightRatio;
        }
        options.inSampleSize = size;
        options.inJustDecodeBounds = false;
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        return BitmapFactory.decodeStream(bais);
    }

3.使用try...catch...抓取OOM異常胧卤。

三唯绍、LruCache算法(LeastRecentlyUsed,最近最少使用算法灌侣,參閱:LRU緩存淘汰算法

Glide擁有三級緩存推捐,即每獲取到一張圖片,都會在內(nèi)存和本地文件進行緩存侧啼,如果下次又用到了同樣的圖片:
1.內(nèi)存有無需要的圖片牛柒?有的話就用,沒有就去本地文件找痊乾。
2.本地文件有無需要的圖片皮壁?有的話就用,沒有就去網(wǎng)絡找哪审。
一共有三個環(huán)節(jié):內(nèi)存 --> 本地文件 --> 網(wǎng)絡蛾魄。
說完三級緩存,接下來再引入一個概念:Lru緩存算法湿滓,如下圖所示滴须。


Lru緩存算法示意圖
劃重點:
1.新數(shù)據(jù)插入到鏈表頭部;
2.每當緩存命中(即緩存數(shù)據(jù)被訪問)叽奥,則將數(shù)據(jù)移到鏈表頭部扔水;
3.當鏈表滿的時候(即達到總緩存數(shù)量上限),將鏈表尾部的數(shù)據(jù)丟棄朝氓。

Glide的內(nèi)存緩存策略基于LruCache算法魔市,android.util包下就有關于Lru緩存算法的實現(xiàn)類LruCache主届。
讓我們看看LruCache的源碼實現(xiàn),首先來看看LruCache的成員變量及構造方法待德。

LruCache.java
    private final LinkedHashMap<K, V> map;//LruCache內(nèi)部基于LinkedHashMap實現(xiàn)
    private int size;//當前已緩存的數(shù)量
    private int maxSize;//可緩存的最大數(shù)量(總緩存容量)
    private int putCount;//加入的緩存數(shù)量
    private int createCount;//創(chuàng)造的緩存數(shù)量(如果取緩存時緩存不存在君丁,則會優(yōu)先創(chuàng)造緩存,下文分析)
    private int evictionCount;//淘汰的數(shù)量(如果超出緩存容量将宪,則最少使用的緩存會被淘汰)
    private int hitCount;//命中數(shù)量(如果取用緩存绘闷,緩存依舊存在,沒有沒淘汰涧偷,則算作命中)
    private int missCount;//未命中數(shù)量(如果取用緩存簸喂,緩存已經(jīng)被淘汰,則算未命中)

    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

留意LruCache構造方法燎潮,首先對maxSize進行了賦值喻鳄。其次,在創(chuàng)建LinkedHashMap<K确封,V>對象的時候除呵,傳入了3個參數(shù):0,0.75F爪喘,true颜曾。這里稍微解釋一下:
第一個參數(shù)為initialCapacity,即初始容量秉剑,定義了LinkedHashMap的初始大小泛豪。
第二個參數(shù)為loadFactor,即加載因子侦鹏,默認值0.75F诡曙,意為如果LinkedHashMap中的元素數(shù)量達到了總容量的75%,就會擴容為原來的兩倍略水。例如价卤,定義一個HashMap,初始容量默認為16渊涝,加載因子0.75F慎璧,那么此HashMap的初始實際容量為12,當HashMap內(nèi)元素數(shù)量達到12時跨释,會自動擴容至2倍胸私,即32。這塊各位讀者可以參閱HashMap源碼鳖谈,日后我也會寫一些HashMap源碼分析岁疼。
第三個參數(shù)為accessOrder,定義了LinkedHashMap<K蚯姆,V>的排序模式五续。當為true時,LinkedHashMap<K龄恋,V>為access-order(訪問順序模式疙驾,也就是LruCache算法的模式),當為false時郭毕,LinkedHashMap<K它碎,V>為insertion-order(插入順序模式)。建議讀者參閱LinkedHashMap<K显押,V>源碼扳肛。
接下來看一下LruCache類中的四個核心方法:獲取數(shù)據(jù)、緩存數(shù)據(jù)乘碑、調整總緩存大小及刪除數(shù)據(jù)挖息。

(一)獲取數(shù)據(jù)
LruCache.java
    //獲取數(shù)據(jù)
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                //命中數(shù)量+1,并返回mapValue
                hitCount++;
                return mapValue;
            }
            missCount++;//未命中數(shù)量+1
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */

        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

對獲取數(shù)據(jù)方法get(K key)進行分析兽肤。首先從LinkedHashMap中get(key)套腹,如果取出數(shù)據(jù)不為null,說明數(shù)據(jù)命中资铡,命中數(shù)計數(shù)+1电禀,同時返回取出的數(shù)據(jù)。如果取出數(shù)據(jù)為null笤休,未命中數(shù)計數(shù)+1尖飞,get(K key)方法繼續(xù)向下執(zhí)行,調用了方法體內(nèi)的create(key)方法店雅。create(key)方法執(zhí)行了什么操作呢政基?查看源碼。

LruCache.java
    /**
     * Called after a cache miss to compute a value for the corresponding key.
     * Returns the computed value or null if no value can be computed. The
     * default implementation returns null.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * <p>If a value for {@code key} exists in the cache when this method
     * returns, the created value will be released with {@link #entryRemoved}
     * and discarded. This can occur when multiple threads request the same key
     * at the same time (causing multiple values to be created), or when one
     * thread calls {@link #put} while another is creating a value for the same
     * key.
     */
    protected V create(K key) {
        return null;
    }

為便于理解底洗,我把原注釋也摘錄了出來腋么。create(K key)方法在獲取數(shù)據(jù)失敗(未命中緩存)的時候調用亥揖,用戶可以覆寫該方法珊擂,在未命中緩存的時候返回特定的數(shù)據(jù),默認情況下返回了null费变。
對接下來的一個同步代碼塊進行分析摧扇,可能讀者對這塊代碼非常疑惑,如下段對map數(shù)據(jù)重新覆蓋的邏輯挚歧。

LruCache.java
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);
            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

首先將創(chuàng)造的緩存數(shù)量計數(shù)+1扛稽。

注:調用HashMap.put(key,value)方法具有返回值滑负。如果原本HashMap<String在张,String>中key = “key”用含,對應的value = “preValue”,調用put(“key”帮匾,“newValue”)方法后啄骇,則put()方法會返回“preValue”,現(xiàn)HashMap中的該鍵值對為:key = “key”瘟斜,對應的value = “newValue”缸夹。即如果key對應的value原本就有值,若調用put()方法放入新value螺句,則put()方法會返回原本的舊value虽惭。

回到源碼中,調用map.put(key, createdValue)方法蛇尚,返回該key值的原本數(shù)據(jù)mapValue芽唇。如果原本的mapValue不為null,則再次調用put(key佣蓉,mapValue)將原本數(shù)據(jù)mapValue放回去披摄。這里會比較疑惑,為什么mapValue可能不為null勇凭?之前調用get(key)方法不是已經(jīng)說明該key對應的mapValue為null了嗎疚膊?為什么還要用mapValue覆蓋掉createdValue?

這里體現(xiàn)了源碼作者的嚴謹性虾标。前文已經(jīng)說過寓盗,在這塊代碼之前已經(jīng)調用了 create(key)方法,默認返回了null璧函。而實際情況中傀蚌,用戶可能覆寫該方法,在未命中緩存的情況下蘸吓,返回自定義的數(shù)據(jù)善炫。而用戶覆寫的邏輯可能是耗時操作,同時此處的代碼并不是線程安全的库继,因此在調用上述同步代碼塊的時候箩艺,map.put(key, createdValue)方法可能會返回曾經(jīng)已經(jīng)放進去的mapValue。那么接下來的操作就是將原本放進去的mapValue再次覆蓋createdValue宪萄,即再次調用map.put(key, mapValue)艺谆,銷毀掉createdValue。這里請讀者仔細體悟拜英。

size += safeSizeOf(key, createdValue)的作用是重新計算此時已經(jīng)占用的緩存數(shù)量静汤。接下來if(mapValue != null)的分支中執(zhí)行了entryRemoved(false, key, createdValue, mapValue)方法,這是要實現(xiàn)啥?看看源碼:

LruCache.java
    /**
     * Called for entries that have been evicted or removed. This method is
     * invoked when a value is evicted to make space, removed by a call to
     * {@link #remove}, or replaced by a call to {@link #put}. The default
     * implementation does nothing.
     *
     * <p>The method is called without synchronization: other threads may
     * access the cache while this method is executing.
     *
     * @param evicted true if the entry is being removed to make space, false
     *     if the removal was caused by a {@link #put} or {@link #remove}.
     * @param newValue the new value for {@code key}, if it exists. If non-null,
     *     this removal was caused by a {@link #put}. Otherwise it was caused by
     *     an eviction or a {@link #remove}.
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

看原注釋虫给,了解到這是一個空實現(xiàn)藤抡,如果有需要的話,用戶可以覆寫這個方法抹估,這個方法會在緩存數(shù)據(jù)被淘汰或移除時調用杰捂。回到之前的代碼棋蚌,if(mapValue != null)的else分支執(zhí)行了trimToSize(maxSize)來對超過最大緩存數(shù)量外的緩存數(shù)據(jù)進行了淘汰,下文再對trimToSize(maxSize)方法進行分析挨队。

(二)緩存數(shù)據(jù)
LruCache.java
    //緩存數(shù)據(jù)   
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);
        return previous;
    }

首先對加入的緩存計數(shù)putCount+1谷暮,并執(zhí)行size += safeSizeOf(key, value)對已緩存數(shù)據(jù)容量重新計算。然后調用map.put(key, value)獲取原本舊緩存previous盛垦。如果previous不為null湿弦,需要再次執(zhí)行size -= safeSizeOf(key, previous)對已緩存數(shù)據(jù)容量重新計算。
entryRemoved(false, key, previous, value)方法前文已經(jīng)分析過腾夯,跳過颊埃。
最終又調用了trimToSize(maxSize)對超過最大緩存數(shù)量外的緩存數(shù)據(jù)進行了淘汰。

(三)調整緩存大小
LruCache.java
    //調整總緩存大小
    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

這塊邏輯比較簡單蝶俱,核心點是調用 map.eldest()獲取最老的緩存鍵值對班利。從map中remove該鍵值對,重新計算已緩存數(shù)量榨呆,并對淘汰緩存數(shù)量計數(shù)evictionCount+1罗标。

(四)刪除數(shù)據(jù)
LruCache.java
    //刪除數(shù)據(jù)
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }

最后看一下刪除數(shù)據(jù)的邏輯,比較簡單积蜻,留給讀者自行閱讀闯割。

這篇文章到此結束,下篇文章將對Glide的into()方法進行分析竿拆,這將是Glide中最復雜的方法宙拉。再會。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丙笋,一起剝皮案震驚了整個濱河市谢澈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌不见,老刑警劉巖澳化,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異稳吮,居然都是意外死亡缎谷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來列林,“玉大人瑞你,你說我怎么就攤上這事∠3眨” “怎么了者甲?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長砌创。 經(jīng)常有香客問我虏缸,道長,這世上最難降的妖魔是什么嫩实? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任刽辙,我火速辦了婚禮,結果婚禮上甲献,老公的妹妹穿的比我還像新娘宰缤。我一直安慰自己,他們只是感情好晃洒,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布慨灭。 她就那樣靜靜地躺著,像睡著了一般球及。 火紅的嫁衣襯著肌膚如雪氧骤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天吃引,我揣著相機與錄音语淘,去河邊找鬼。 笑死际歼,一個胖子當著我的面吹牛惶翻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鹅心,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼吕粗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旭愧?” 一聲冷哼從身側響起颅筋,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎输枯,沒想到半個月后议泵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡桃熄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年先口,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡碉京,死狀恐怖厢汹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谐宙,我是刑警寧澤烫葬,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站凡蜻,受9級特大地震影響搭综,放射性物質發(fā)生泄漏。R本人自食惡果不足惜划栓,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一设凹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茅姜,春花似錦、人聲如沸月匣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锄开。三九已至素标,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萍悴,已是汗流浹背头遭。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留癣诱,地道東北人计维。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像撕予,于是被迫代替她去往敵國和親鲫惶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

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