“雪糕刺客”你聽說過,Bitmap這個(gè)“內(nèi)存刺客”你也要小心(上)~

1.png

寫在前面

雪糕刺客是最近被網(wǎng)友們玩壞了的梗,指的是那些以平平無奇的外表混跡于眾多平價(jià)雪糕之中的貴價(jià)雪糕。由于沒有明確標(biāo)明價(jià)格,通常要等到結(jié)賬的時(shí)候才會(huì)發(fā)現(xiàn),猶如一個(gè)潛藏于普通人群中的刺客般,伺機(jī)對(duì)那些大意的顧客們的錢包刺上一劍,因此得名。

而在Android中,也有這么一個(gè)內(nèi)存刺客,其作為我們?nèi)粘i_發(fā)中經(jīng)常接觸的對(duì)象之一,卻常常因?yàn)槭褂梅绞降牟划?dāng),時(shí)不時(shí)地就會(huì)給我們有限的內(nèi)存來上一個(gè)背刺,甚至毫不留情地就給我們拋出一個(gè)OOM,它,就是Bitmap

為了講好Bitmap這個(gè)話題,本系列文章將分為上下兩篇,上篇從圖像基礎(chǔ)知識(shí)出發(fā),結(jié)合源碼講解Bitmap內(nèi)存的計(jì)算方式;下篇?jiǎng)t基于Android系統(tǒng)提供的API,講解在實(shí)際開發(fā)中如何管理好Bitmap的內(nèi)存,包括縮放、緩存、內(nèi)存復(fù)用等,敬請(qǐng)期待。

本文為上篇,開始之前,先奉上的思維導(dǎo)圖一張,方便后續(xù)復(fù)習(xí):

2.png

從一個(gè)問題出發(fā)

假設(shè)有這么一張PNG格式的圖片,其大小為15.3KB,尺寸為96x96,色深為32 bit,放到xhdpi目錄下,并加載到一臺(tái)dpi為480的Android設(shè)備上顯示,那么請(qǐng)問,該圖片實(shí)際會(huì)占用多大的內(nèi)存?

3.png

如果你回答不了這個(gè)問題,那你就有必要深入往下讀了。

壓縮格式大小≠占用內(nèi)存大小

首先我們要明確的是,無論是JPEG還是PNG,它們本質(zhì)上都是一種壓縮格式,壓縮的目的是為了降低存儲(chǔ)和傳輸?shù)某杀?/strong>。

區(qū)別就在于:

JPEG是一種有損壓縮格式,壓縮比大,壓縮后的體積比較小,但其高壓縮率是通過去除冗余的圖像數(shù)據(jù)進(jìn)行的,因此解壓后無法還原出完整的原始圖像數(shù)據(jù)。

PNG則是一種無損壓縮格式,不會(huì)損失圖片質(zhì)量,解壓后能還原出完整的原始圖像數(shù)據(jù),但也因此壓縮比小,壓縮后的體積仍然很大。

開篇問題中所特意強(qiáng)調(diào)的圖片大小,實(shí)際指的就是壓縮格式文件的大小。而問題最后所問的圖片實(shí)際占用的內(nèi)存,指的則是解壓縮后顯示在設(shè)備屏幕上的原始圖像數(shù)據(jù)所占用的內(nèi)存

在實(shí)際的Android開發(fā)中,我們經(jīng)常直接接觸到的原始圖像數(shù)據(jù),就是通過各種decode方法解碼出的Bitmap對(duì)象

Bitmap即位圖,它還有另外一個(gè)名稱叫做點(diǎn)陣圖,相對(duì)來說,點(diǎn)陣圖這個(gè)名稱更能表述Bitmap的特征。

點(diǎn)指的是像素點(diǎn)指的是陣列。點(diǎn)陣圖,就是以像素為最小單位構(gòu)成的圖,縮放會(huì)失真。每個(gè)像素實(shí)則都是一個(gè)非常小的正方形,并被分配不同的顏色,然后通過不同的排列來構(gòu)成像素陣列,最終呈現(xiàn)出完整的圖像

4.png

那么每個(gè)像素是如何存儲(chǔ)自己的顏色信息的呢?這涉及到圖片的色深。

色深是什么?

色深,又叫色彩深度(Color Depth)。假設(shè)色深的數(shù)值為n,代表每個(gè)像素會(huì)采用n個(gè)二進(jìn)制位來存儲(chǔ)顏色信息,也即2的n次方,表示的是每個(gè)像素能顯示2^n種顏色**。

常見的色深有:

  • 1 bit:只能顯示黑與白兩個(gè)中的一個(gè)。因?yàn)樵谏顬?的情況下,每個(gè)像素只能存儲(chǔ)2^1=2種顏色。

  • 8 bit:可以存儲(chǔ)2^8=256種的顏色,典型的如GIF圖像的色深就為8 bit。

  • 24 bit:可以存儲(chǔ)2^24=16,777,216種的顏色。每個(gè)像素的顏色由紅(Red)、綠(Green)、藍(lán)(Blue)3個(gè)顏色通道合成,每個(gè)顏色通道用8bit來表示,其取值范圍是:

    • 二進(jìn)制:00000000~11111111
    • 十進(jìn)制:0~255
    • 十六進(jìn)制:00~FF

    這里很自然地就讓人聯(lián)想起Android中常用于表示顏色兩種形式,即:

    • Color.rgb(float red, float green, float blue),對(duì)應(yīng)十進(jìn)制
    • Color.parceColor(String colorString),對(duì)應(yīng)十六進(jìn)制
  • 32 bit:在24位的基礎(chǔ)上,增加多8個(gè)位的透明通道。

色深會(huì)影響圖片的整體質(zhì)量,我們可以來看同一張圖片在不同色深下的表現(xiàn):

5.png
6.png
7.png
8.png
9.png

可以看出,色深越大,能表示的顏色越豐富,圖片也就越鮮艷,顏色過渡就越平滑。但相對(duì)的,圖片的體積也會(huì)增加,因?yàn)?strong>每個(gè)像素必須存儲(chǔ)更多的顏色信息

Android中與色深配置相關(guān)的類是Bitmap.Config,其取值會(huì)直接影響位圖的質(zhì)量(色彩深度)以及顯示透明/半透明顏色的能力。在Android 2.3(API 級(jí)別 9)及更高版本中的默認(rèn)配置是ARGB_8888,也即32 bit的色深,1 byte = 8 bit,因此該配置下每個(gè)像素的大小為4 byte。

位圖內(nèi)存 = 像素?cái)?shù)量(分辨率) * 每個(gè)像素的大小,想要進(jìn)一步計(jì)算加載位圖所需要的內(nèi)存,我們還需要得知像素的總數(shù)量,而描述像素?cái)?shù)量的說法就是分辨率。

分辨率是什么?

如果說,色深決定了位圖顏色的豐富程度,那么分辨率決定的則是位圖圖像細(xì)節(jié)的精細(xì)程度圖像的分辨率越高,所包含的像素就越多,圖像也就越清晰,同樣的,它也會(huì)相應(yīng)增加圖片的體積

通常,我們用每一個(gè)方向上的像素?cái)?shù)量來表示分辨率,也即水平像素?cái)?shù)×垂直像素?cái)?shù),比如320×240,640×480,1280×1024等。

一張分辨率為640x480的圖片,其像素?cái)?shù)量就達(dá)到了307200,也就是我們常說的30萬像素。

現(xiàn)在,我們明白了公式中2個(gè)變量的含義,就可以代入開篇問題中的例子來計(jì)算位圖內(nèi)存:

96 * 96 * 4 byte = 36864 bytes = 36KB

Bitmap提供了兩個(gè)方法用于獲取系統(tǒng)為該Bitmap存儲(chǔ)像素所分配的內(nèi)存大小,分別為:

public int getByteCount ()
public int getAllocationByteCount ()

一般情況下,兩個(gè)方法返回的值是相同的。但如果我們手動(dòng)重新配置了Bitmap的屬性(寬、高、Bitmap.Config等),或者將BitmapFactory.Options.inBitmap屬性設(shè)為true以支持其他更小的Bitmap復(fù)用其內(nèi)存時(shí),那么getAllocationByteCount ()返回的值就有可能會(huì)大于getByteCount()。

我們暫時(shí)不考慮以上兩種場(chǎng)景,所以直接選擇調(diào)用getByteCount方法 ()來獲取為Bitmap分配的字節(jié)數(shù),得到的結(jié)果是:82944 bytes = 81KB。

可以看到,getByteCount方法返回的值與我們的計(jì)算結(jié)果有差異,是我們的計(jì)算公式有問題嗎?

探究getByteCount()的計(jì)算公式

為了驗(yàn)證我們的計(jì)算公式是否準(zhǔn)確,我們需要深入getByteCount()方法的源碼進(jìn)行探究。

public final int getByteCount() {
    if (mRecycled) {
        Log.w(TAG, "Called getByteCount() on a recycle()'d bitmap! "
                + "This is undefined behavior!");
        return 0;
    }
    // int result permits bitmaps up to 46,340 x 46,340
    return getRowBytes() * getHeight();
}

可以看到,getByteCount()方法的返回值是每一行的字節(jié)數(shù) * 高度,那么每一行的字節(jié)數(shù)又是怎么計(jì)算的呢?

public final int getRowBytes() {
   if (mRecycled) {
          Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
   }
   return nativeRowBytes(mFinalizer.mNativeBitmap);
}

正如你所見,getRowBytes()方法的實(shí)現(xiàn)是在Native層。先別灰心,接下來坐好扶穩(wěn)了,我們省去一些不重要的步驟,乘坐飛船一路跨越Bitmap.cpp、SkBitmap.h,途徑SkBitmap.cpp時(shí)稍微停下:

size_t SkBitmap::ComputeRowBytes(Config c, int width) {
    return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width);
}

并最終到達(dá)SkImageInfo.h:

static int SkColorTypeBytesPerPixel(SkColorType ct) {
   static const uint8_t gSize[] = {
    0,  // Unknown
    1,  // Alpha_8
    2,  // RGB_565
    2,  // ARGB_4444
    4,  // RGBA_8888
    4,  // BGRA_8888
    1,  // kIndex_8
  };
   SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gSize) == (size_t)(kLastEnum_SkColorType + 1),
                size_mismatch_with_SkColorType_enum);

   SkASSERT((size_t)ct < SK_ARRAY_COUNT(gSize));
   return gSize[ct];
}

static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) {
    return width * SkColorTypeBytesPerPixel(ct);
}

都說正確清晰的函數(shù)名有替代注釋的作用,這就是優(yōu)秀的典范。

讓我們把目光停留在width * SkColorTypeBytesPerPixel(ct)這一行,不難看出,其計(jì)算方式是先根據(jù)顏色類型獲取每個(gè)像素對(duì)應(yīng)的字節(jié)數(shù),再去乘以其寬度

那么,結(jié)合Bitmap.java的getByteCount()方法的實(shí)現(xiàn),我們最終得出,系統(tǒng)為Bitmap存儲(chǔ)像素所分配的內(nèi)存大小 = 寬度 * 每個(gè)像素的大小 * 高度,與我們上面的計(jì)算公式一致。

公式?jīng)]錯(cuò),那問題究竟出在哪里呢?

其實(shí),如果我們的圖片是從磁盤、網(wǎng)絡(luò)等地方獲取的,理論上確實(shí)是按照上面的公式那樣計(jì)算沒錯(cuò)。但你還記得嗎?我們?cè)陂_篇的問題中,還特意強(qiáng)調(diào)了圖片是放在xhdpi目錄下的。在Android設(shè)備上,這種情況下計(jì)算位圖內(nèi)存,還有一個(gè)維度要考慮進(jìn)來,那就是像素密度

像素密度是什么?

像素密度指的是屏幕單位面積內(nèi)的像素?cái)?shù),稱為dpi(dots per inch,每英寸點(diǎn)數(shù))。當(dāng)兩個(gè)設(shè)備的尺寸相同而像素密度不同時(shí),圖像的效果呈現(xiàn)如下:

10.png

是不是感覺跟分辨率的概念有點(diǎn)像?區(qū)別就在于,前者是屏幕單位面積內(nèi)的像素?cái)?shù),后者是屏幕上的總像素?cái)?shù)

由于Android是開源的,任何硬件制造商都可以制造搭載Android系統(tǒng)的設(shè)備,因此從手表、手機(jī)到平板電腦再到電視,各種屏幕尺寸和屏幕像素密度的設(shè)備層出不窮。

11.png

為了優(yōu)化不同屏幕配置下的用戶體驗(yàn),確保圖像能在所有屏幕上顯示最佳效果,Android建議應(yīng)針對(duì)常見的不同的屏幕尺寸和屏幕像素密度,提供對(duì)應(yīng)的圖片資源。于是就有了Android工程res目錄下,加上各種配置限定符的drawable/mipmap文件夾。

為了簡(jiǎn)化不同的配置,Android針對(duì)不同像素密度范圍進(jìn)行了歸納分組,如下:

12.png

我們通常選取中密度 (mdpi) 作為基準(zhǔn)密度(1倍圖),并保持ldpi~xxxhdpi這六種主要密度之間 3:4:6:8:12:16 的縮放比,來放置相應(yīng)尺寸的圖片資源。

例如,在創(chuàng)建Android工程時(shí)IDE默認(rèn)為我們添加的ic_launcher圖標(biāo),就遵循了這個(gè)規(guī)則。該圖標(biāo)在中密度 (mdpi)目錄下的大小為48x48,在其他各種密度的目錄下的大小則分別為:

  • 36x36 (0.75x) - 低密度 (ldpi)
  • 48x48(1.0x 基準(zhǔn))- 中密度 (mdpi)
  • 72x72 (1.5x) - 高密度 (hdpi)
  • 96x96 (2.0x) - 超高密度 (xhdpi)
  • 144x144 (3.0x) - 超超高密度 (xxhdpi)
  • 192x192 (4.0x) - 超超超高密度 (xxxhdpi)

當(dāng)我們引用該圖標(biāo)時(shí),系統(tǒng)就會(huì)根據(jù)所運(yùn)行設(shè)備屏幕的dpi,與不同密度目錄名稱中的限定符進(jìn)行比較,來選取最符合當(dāng)前設(shè)備的圖片資源。如果在該密度目錄下沒有找到合適的圖片資源,系統(tǒng)會(huì)有對(duì)應(yīng)的規(guī)則查找另外一個(gè)可能的匹配資源,并對(duì)其進(jìn)行相應(yīng)的縮放,以適配屏幕,由此可能造成圖片有明顯的模糊失真

13.png

那么,具體的查找規(guī)則是怎樣的呢?

Android查找最佳匹配資源的規(guī)則

一般來說,Android會(huì)更傾向于縮小較大的原始圖像,而非放大較小的原始圖像。在此前提下:

  • 假設(shè)最接近設(shè)備屏幕密度的目錄選項(xiàng)為xhdpi,如果圖片資源存在,則匹配成功;
  • 如果不存在,系統(tǒng)就會(huì)從更高密度的資源目錄下查找,依次為xxhdpi、xxxhdpi;
  • 如果還不存在,系統(tǒng)就會(huì)從像素密度無關(guān)的資源目錄nodpi下查找;
  • 如果還不存在,系統(tǒng)就會(huì)向更低密度的資源目錄下查找,依次為hdpi、mdpi、ldpi。

那么,當(dāng)匹配到其他密度目錄下的圖片資源后,對(duì)于原始圖像的放大或縮小,Android是怎么實(shí)現(xiàn)的呢?又會(huì)對(duì)加載位圖所需要的內(nèi)存有什么影響呢?

想解決這些疑惑,我們還是得從源碼中找尋答案。

decode*方法的貓膩

眾所周知,在Android中要讀取drawable/mipmap目錄下的圖片資源,需要用到的是BitmapFactory類下的decodeResource方法:

    public static Bitmap decodeResource(Resources res, int id, Options opts) {
        ...
        final TypedValue value = new TypedValue();
        is = res.openRawResource(id, value);

        bm = decodeResourceStream(res, value, is, null, opts);
        ...
    }

decodeResource方法的主要工作,就只是調(diào)用Resource#openRawResource方法讀取原始圖片資源,同時(shí)傳遞一個(gè)TypedValue對(duì)象用于持有圖片資源的相關(guān)信息,并返回一個(gè)輸入流作為內(nèi)部繼續(xù)調(diào)用decodeResourceStream方法的參數(shù)。

    public static Bitmap decodeResourceStream(Resources res, TypedValue value,InputStream is, Rect pad, Options opts) {
        if (opts == null) {
            opts = new Options();
        }

        if (opts.inDensity == 0 && value != null) {
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }
        
        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }
        
        return decodeStream(is, pad, opts);
    }

decodeResourceStream方法的主要工作,則是負(fù)責(zé)Options(解碼選項(xiàng))類2個(gè)重要參數(shù)inDensity和inTargetDensity的初始化,其中:

  • inDensity代表的是Bitmap的像素密度,取決于原始圖片資源所存放的密度目錄。
  • inTargetDensity代表的是Bitmap將繪制到的目標(biāo)的像素密度,通常就是指屏幕的像素密度。

這兩個(gè)參數(shù)起什么作用呢,讓我們繼續(xù)往下看:

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
    ···
    if (is instanceof AssetManager.AssetInputStream) {
        final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
        bm = nativeDecodeAsset(asset, outPadding, opts);
    } else {
        bm = decodeStreamInternal(is, outPadding, opts);
    }
    ···
}
private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
    byte [] tempStorage = null;
    if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
    return nativeDecodeStream(is, tempStorage, outPadding, opts);
}

又見到熟悉的Native層方法了,讓我們重新開動(dòng)星際飛船再次跨越到BitmapFactory.cpp下查看:

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, jobject padding, jobject options) {
    ···
    bitmap = doDecode(env, bufferedStream, padding, options);
    ···
}
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    ····
    float scale = 1.0f;
    ···
    if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
        const int density = env->GetIntField(options, gOptions_densityFieldID);
        const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
        const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
        if (density != 0 && targetDensity != 0 && density != screenDensity) {
            scale = (float) targetDensity / density;
        }
    }
    ···
    const bool willScale = scale != 1.0f;
    ···
    int scaledWidth = decodingBitmap.width();
    int scaledHeight = decodingBitmap.height();
    
    if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
        scaledWidth = int(scaledWidth * scale + 0.5f);
        scaledHeight = int(scaledHeight * scale + 0.5f);
    }
    
    if (options != NULL) {
       env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
       env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
       env->SetObjectField(options, gOptions_mimeFieldID,
       getMimeTypeString(env, decoder->getFormat()));
    }
    ...
}

以上節(jié)選的doDecode方法的部分源碼,就是Android系統(tǒng)如何對(duì)其他密度目錄下的原始圖像進(jìn)行縮放的具體實(shí)現(xiàn),我們來梳理一下它的執(zhí)行邏輯:

  1. 首先,設(shè)置scale值也即初始的縮放比為1。
  2. 取出關(guān)鍵的density值以及targetDensity值,以目標(biāo)像素密度/位圖像素密度重新計(jì)算縮放比。
  3. 如果縮放比不再為1,則說明原始圖像需要進(jìn)行縮放。
  4. 取出待解碼的位圖的寬度,按int(scaledWidth * scale + 0.5f)計(jì)算縮放后的寬度,高度同理。
  5. 重新填充縮放后的寬高回Options。

基于以上內(nèi)容,我們重新調(diào)整下我們的計(jì)算公式:

位圖內(nèi)存 = (位圖寬度 * 縮放比) * 每個(gè)像素的大小 * (位圖高度 * 縮放比)
= (96 * 1.5) * 4 * (96 * 1.5)
= 82944 bytes = 81KB

可以看到,這樣計(jì)算得出來的結(jié)果則與Bitmap#getByteCount()返回的值一致。

總結(jié)

匯總上述的所有內(nèi)容后,我們可以得出結(jié)論,即:

Android系統(tǒng)為Bitmap存儲(chǔ)像素所分配的內(nèi)存大小,取決于以下幾個(gè)因素:

  • 色深,也即每個(gè)像素的大小,對(duì)應(yīng)的是Bitmap.Config的配置。
  • 分辨率,也即像素的總數(shù)量,對(duì)應(yīng)的是Bitmap的高度和寬度
  • 像素密度,對(duì)應(yīng)的是圖片資源所在的密度目錄,以及設(shè)備的屏幕像素密度

由此我們還衍生出其他的結(jié)論,即:

  • 圖片資源放到正確的密度目錄很重要,否則可能對(duì)會(huì)較大尺寸的圖片進(jìn)行不合理的縮放,從而加大不必要的內(nèi)存占用。
  • 如果是為了減少包體積而不想提供所有密度目錄下不同尺寸的圖片,應(yīng)優(yōu)先提供更高密度目錄下的圖片資源,可以避免圖片失真。
  • ...

參考

App resources overview

What is bit depth?

重識(shí)圖片

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,118評(píng)論 0 286
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,347評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評(píng)論 2 370

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