Android中Bitmap內(nèi)存優(yōu)化

Android開發(fā)中,Bitmap是經(jīng)常會(huì)遇到的對(duì)象,特別是在列表圖片展示、大圖顯示等界面。而Bitmap實(shí)實(shí)在在是內(nèi)存使用的“大客戶”。如何更好的使用Bitmap,減少其對(duì)App內(nèi)存的使用,是Android優(yōu)化方面不可回避的問題。因此,本文從常規(guī)的Bitmap使用,到Bitmap內(nèi)存計(jì)算進(jìn)行了介紹,最后分析了Bitmap的源碼和其內(nèi)存模型在不同版本上的變化。

Bitmap的使用

一般來說,一個(gè)對(duì)象的使用,我們會(huì)嘗試?yán)闷錁?gòu)造函數(shù)去生成這個(gè)對(duì)象。在Bitmap中,其構(gòu)造函數(shù):

// called from JNI
    Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) 

通過構(gòu)造函數(shù)的注釋,得知這是一個(gè)給native層調(diào)用的方法,因此可以知道Bitmap的創(chuàng)建將會(huì)涉及到底層庫(kù)的支持。為了方便從不同來源來創(chuàng)建Bitmap,Android中提供了BitmapFactory工具類。BitmapFactory類中有一系列的decodeXXX方法,用于解析資源文件、本地文件、流等方式,基本流程都很類似,讀取目標(biāo)文件,轉(zhuǎn)換成輸入流,調(diào)用native方法解析流,雖然Java層代碼沒有體現(xiàn),但是我們可以猜想到,最后native方法解析完成后,必然會(huì)通過JNI調(diào)用Bitmap的構(gòu)造函數(shù),完成Java層的Bitmap對(duì)象創(chuàng)建。

// BitmapFactory部分代碼:
public static Bitmap decodeResource(Resources res, int id)
public static Bitmap decodeStream(InputStream is)
private static native Bitmap nativeDecodeStream

native層的代碼稍后我們?cè)诳矗葟腏ava層來看看常規(guī)的使用。典型的一個(gè)例子是,當(dāng)我們需要從本地Resource中加載一個(gè)圖片,并展示出來,我們可以通過BitmapFacotry來完成:

Bitmap bitmapDecode = BitmapFactory.decodeResource(getResources(), resId);
imageView.setImageBitmap(bitmapDecode);

當(dāng)然,這里簡(jiǎn)單的使用imageView.setImageResource(int resId)也能實(shí)現(xiàn)一樣的效果,實(shí)際上setImageResource方法只是封裝了bitmap的讀入、解析的過程,并且這個(gè)過程是在UI線程完成的,對(duì)于性能是有所影響的。另外,也對(duì)接下來討論的內(nèi)容,Bitmap占用的內(nèi)存有影響。

Bitmap到底占用多大的內(nèi)存

Bitmap作為位圖,需要讀入一張圖片每一個(gè)像素點(diǎn)的數(shù)據(jù),其主要占用內(nèi)存的地方也正是這些像素?cái)?shù)據(jù)。對(duì)于像素?cái)?shù)據(jù)總大小,我們可以猜想為:像素總數(shù)量 × 每個(gè)像素的字節(jié)大小,而像素總數(shù)量在矩形屏幕表現(xiàn)下,應(yīng)該是:橫向像素?cái)?shù)量 × 縱向像素?cái)?shù)量,結(jié)合得到:

Bitmap內(nèi)存占用 ≈ 像素?cái)?shù)據(jù)總大小 = 橫向像素?cái)?shù)量 × 縱向像素?cái)?shù)量 × 每個(gè)像素的字節(jié)大小

單個(gè)像素的字節(jié)大小

單個(gè)像素的字節(jié)大小由Bitmap的一個(gè)可配置的參數(shù)Config來決定。
Bitmap中,存在一個(gè)枚舉類Config,定義了Android中支持的Bitmap配置:

Config 占用字節(jié)大小(byte) 說明
ALPHA_8 (1) 1 單透明通道
RGB_565 (3) 2 簡(jiǎn)易R(shí)GB色調(diào)
ARGB_4444 (4) 2 已廢棄
ARGB_8888 (5) 4 24位真彩色
RGBA_F16 (6) 8 Android 8.0 新增(更豐富的色彩表現(xiàn)HDR)
HARDWARE (7) Special Android 8.0 新增 (Bitmap直接存儲(chǔ)在graphic memory)注1

注1:關(guān)于Android 8.0中新增的這個(gè)配置,stackoverflow已經(jīng)有相關(guān)問題,可以關(guān)注下。

之前我們分析到,Bitmap的decode實(shí)際上是在native層完成的,因此在native層也存在對(duì)應(yīng)的Config枚舉類。
一般使用時(shí),我們并未關(guān)注這個(gè)配置,在BitmapFactory中,有:

  * Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by default.
  */
  public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;

因此,Android系統(tǒng)中,默認(rèn)Bitmap加載圖片,使用24位真彩色模式。

Bitmap占用內(nèi)存大小實(shí)例

首先準(zhǔn)備了一張800×600分辨率的jpg圖片,大小約135k,放置于res/drawable文件夾下:


awesomecoder.jpg

并將其加載到一個(gè)200dp×300dp大小的ImageView中,使用BitmapFactory。

Bitmap bitmapDecode = BitmapFactory.decodeResource(getResources(), resId);
imageView.setImageBitmap(bitmapDecode);

打印出相關(guān)信息:


nodpis.png

圖中顯示了從資源文件中decode得到的bitmap的長(zhǎng)、寬和占用內(nèi)存大小(byte)等信息。
首先,從數(shù)據(jù)上可以驗(yàn)證:

17280000 = 2400 * 1800 * 4

這意味著,為了將單張800 * 600 的圖片加載到內(nèi)存當(dāng)中,付出了近17.28M的代價(jià),即使現(xiàn)在手機(jī)運(yùn)存普遍上漲,這樣的開銷也是無(wú)法接受的,因此,對(duì)于Bitmap的使用,是需要非常小心的。好在,目前主流的圖像加載庫(kù)(Glide、Fresco等)基本上都不在需要開發(fā)者去關(guān)心Bitmap內(nèi)存占用問題。
先暫時(shí)回到Bitmap占用內(nèi)存的計(jì)算上來,對(duì)比之前定義的公式和源圖片的尺寸數(shù)據(jù),我們會(huì)發(fā)現(xiàn),這張800 * 600大小的圖片,decode到內(nèi)存中的Bitmap的橫縱像素?cái)?shù)量實(shí)際是:2400 * 1800,相當(dāng)于縮放了3倍大小。為了探究這縮放來自何處,我們開始跟蹤源碼:之前提到過,Bitmap的decode過程實(shí)際上是在native層完成的,為此,需要從BitmapFactory.cpp#nativeDecodeXXX方法開始跟蹤,這里省略其他decode代碼,直接貼出和縮放相關(guān)的代碼如下:

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;
    }
}
...
int scaledWidth = decoded->width();
int scaledHeight = decoded->height();

if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}
...
if (willScale) {
    const float sx = scaledWidth / float(decoded->width());
    const float sy = scaledHeight / float(decoded->height());
    bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight);
    bitmap->allocPixels(&javaAllocator, NULL);
    bitmap->eraseColor(0);
    SkPaint paint;
    paint.setFilterBitmap(true);
    SkCanvas canvas(*bitmap);
    canvas.scale(sx, sy);
    canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
}

從上述代碼中,我們看到bitmap最終通過canvas繪制出來,而canvas在繪制之前,有一個(gè)scale的操作,scale的值由

scale = (float) targetDensity / density;

這一行代碼決定,即縮放的倍率和targetDensity和density相關(guān),而這兩個(gè)參數(shù)都是從傳入的options中獲取到的。這時(shí)候,需要回到Java層,看看options這個(gè)對(duì)象的定義和賦值。

BitmapFactory#Options

Options是BitmapFactory中的一個(gè)靜態(tài)內(nèi)部類,用于配置Bitmap在decode時(shí)的一些參數(shù)。

// native層doDecode方法,傳入了Options參數(shù)
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options)

其內(nèi)部有很多可配置的參數(shù),下面的類圖,列舉出了部分常用的參數(shù)。


BitmapFactory.Options.png

我們先關(guān)注之前提到的幾個(gè)密度相關(guān)的參數(shù),通過閱讀源碼的注釋,大概可以知道這三個(gè)密度參數(shù)代表的涵義:

  • inDensity:Bitmap位圖自身的密度、分辨率
  • inTargetDensity: Bitmap最終繪制的目標(biāo)位置的分辨率
  • inScreenDensity: 設(shè)備屏幕分辨率

其中inDensity和圖片存放的資源文件的目錄有關(guān),同一張圖片放置在不同目錄下會(huì)有不同的值:

density 0.75 1 1.5 2 3 3.5 4
densityDpi 120 160 240 320 480 560 640
DpiFolder ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi xxxxhdpi

inTargetDensity和inScreenDensity一般來說,很少手動(dòng)去賦值,默認(rèn)情況下,是和設(shè)備分辨率保持一致。為此,我在手機(jī)(紅米4,Android 6.0系統(tǒng),設(shè)備dpi 480)上測(cè)試加載不同資源文件下的bitmap的參數(shù),結(jié)果見下圖:


對(duì)比不同資源目錄.png

以上可以驗(yàn)證幾個(gè)結(jié)論:

  • 同一張圖片,放在不同資源目錄下,其分辨率會(huì)有變化,
  • bitmap分辨率越高,其解析后的寬高越小,甚至?xí)∮趫D片原有的尺寸(即縮放),從而內(nèi)存占用也相應(yīng)減少
  • 圖片不特別放置任何資源目錄時(shí),其默認(rèn)使用mdpi分辨率:160
  • 資源目錄分辨率和設(shè)備分辨率一致時(shí),圖片尺寸不會(huì)縮放

因此,關(guān)于Bitmap占用內(nèi)存大小的公式,從之前:

Bitmap內(nèi)存占用 ≈ 像素?cái)?shù)據(jù)總大小 = 橫向像素?cái)?shù)量 × 縱向像素?cái)?shù)量 × 每個(gè)像素的字節(jié)大小

可以更細(xì)化為:

Bitmap內(nèi)存占用 ≈ 像素?cái)?shù)據(jù)總大小 = 圖片寬 × 圖片高× (設(shè)備分辨率/資源目錄分辨率)^2 × 每個(gè)像素的字節(jié)大小

對(duì)于本節(jié)中最開始的例子,如下:

17,280,000 = 800 * 600 * (480 / 160 )^2 * 4

Bitmap內(nèi)存優(yōu)化

圖片占用的內(nèi)存一般會(huì)分為運(yùn)行時(shí)占用的運(yùn)存和存儲(chǔ)時(shí)本地開銷(反映在包大小上),這里我們只關(guān)注運(yùn)行時(shí)占用內(nèi)存的優(yōu)化。
在上一節(jié)中,我們看到對(duì)于一張800 * 600 大小的圖片,不加任何處理直接解析到內(nèi)存中,將近占用了17.28M的內(nèi)存大小。想象一下這樣的開銷發(fā)生在一個(gè)圖片列表中,內(nèi)存占用將達(dá)到非常夸張的地步。從之前Bitmap占用內(nèi)存的計(jì)算公式來看,減少內(nèi)存主要可以通過以下幾種方式:

  1. 使用低色彩的解析模式,如RGB565,減少單個(gè)像素的字節(jié)大小
  2. 資源文件合理放置,高分辨率圖片可以放到高分辨率目錄下
  3. 圖片縮小,減少尺寸

第一種方式,大約能減少一半的內(nèi)存開銷。Android默認(rèn)是使用ARGB8888配置來處理色彩,占用4字節(jié),改用RGB565,將只占用2字節(jié),代價(jià)是顯示的色彩將相對(duì)少,適用于對(duì)色彩豐富程度要求不高的場(chǎng)景。
第二種方式,和圖片的具體分辨率有關(guān),建議開發(fā)中,高分辨率的圖像應(yīng)該放置到合理的資源目錄下,注意到Android默認(rèn)放置的資源目錄是對(duì)應(yīng)于160dpi,目前手機(jī)屏幕分辨率越來越高,此處能節(jié)省下來的開銷也是很可觀的。理論上,圖片放置的資源目錄分辨率越高,其占用內(nèi)存會(huì)越小,但是低分辨率圖片會(huì)因此被拉伸,顯示上出現(xiàn)失真。另一方面,高分辨率圖片也意味著其占用的本地儲(chǔ)存也變大。
第三種方式,理論上根據(jù)適用的環(huán)境,是可以減少十幾倍的內(nèi)存使用的,它基于這樣一個(gè)事實(shí):源圖片尺寸一般都大于目標(biāo)需要顯示的尺寸,因此可以通過縮放的方式,來減少顯示時(shí)的圖片寬高,從而大大減少占用的內(nèi)存。

前兩種方式,相對(duì)比較簡(jiǎn)單。第三種方式會(huì)涉及到一些編碼,目前也有很多典型的使用方式,如下:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), resId,options);
options.inJustDecodeBounds = false;
options.inSampleSize = BitmapUtil.computeSampleSize(options, -1, imageView.getWidth() * imageView.getHeight());
Bitmap newBitmap = BitmapFactory.decodeResource(getResources(), resId, options);

原理很簡(jiǎn)單,充分利用了Options類里的參數(shù)設(shè)置,也可以從native底層源碼上看到對(duì)應(yīng)的邏輯。第一次解析bitmap只獲取尺寸信息,不生成像素?cái)?shù)據(jù),繼而比較bitmap尺寸和目標(biāo)尺寸得到縮放倍數(shù),第二次根據(jù)縮放倍數(shù)去解析我們實(shí)際需要的尺寸大小。

// Apply a fine scaling step if necessary.
    if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
        willScale = true;
        scaledWidth = codec->getInfo().width() / sampleSize;
        scaledHeight = codec->getInfo().height() / sampleSize;
    }
優(yōu)化后.png

上圖是使用上述手段優(yōu)化后的結(jié)果,可以看到現(xiàn)在占用的內(nèi)存大小大約為960KB,從優(yōu)化后的寬高來看,第三種方式并沒有效果。應(yīng)為目標(biāo)ImageView尺寸也不小,而inSampleSize的值必須是2的整數(shù)冪,因此計(jì)算得到的值還是1。

PS: Bitmap內(nèi)存占用的優(yōu)化還有一個(gè)方式是復(fù)用和緩存

不同Android版本時(shí)的Bitmap內(nèi)存模型

我們知道Android系統(tǒng)中,一個(gè)進(jìn)程的內(nèi)存可以簡(jiǎn)單分為Java內(nèi)存和native內(nèi)存兩部分,而Bitmap對(duì)象占用的內(nèi)存,有Bitmap對(duì)象內(nèi)存和像素?cái)?shù)據(jù)內(nèi)存兩部分,在不同的Android系統(tǒng)版本中,其所存放的位置也有變化。Android Developers上列舉了從API 8 到API 26之間的分配方式:

API級(jí)別 API 10 - API 11 ~ API 25 API 26 +
Bitmap對(duì)象存放 Java heap Java heap Java heap
像素(pixel data)數(shù)據(jù)存放 native heap Java heap native heap

可以看到,最新的Android O之后,谷歌又把像素存放的位置,從java 堆改回到了 native堆。API 11的那次改動(dòng),是源于native的內(nèi)存釋放不及時(shí),會(huì)導(dǎo)致OOM,因此才將像素?cái)?shù)據(jù)保存到Java堆,從而保證Bitmap對(duì)象釋放時(shí),能夠同時(shí)把像素?cái)?shù)據(jù)內(nèi)存也釋放掉。


Android 6.0內(nèi)存變化.png

Android 8.0內(nèi)存變化.png

上面兩幅圖展示了不同系統(tǒng),加載圖片后,內(nèi)存的變化,8.0的截圖比較模糊。途中淺藍(lán)色對(duì)應(yīng)的是Java heap使用,深藍(lán)色對(duì)應(yīng)的是native heap的使用。
跟蹤一下8.0的native源碼來看看具體的變化:

// BitmapFactory.cpp
    if (!decodingBitmap.setInfo(bitmapInfo) ||
            !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable.get())) {
        // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
        // should only only fail if the calculated value for rowBytes is too
        // large.
        // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
        // native heap, or the recycled javaBitmap being too small to reuse.
        return nullptr;
    }

// Graphics.cpp
bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
    mStorage = android::Bitmap::allocateHeapBitmap(bitmap, sk_ref_sp(ctable));
    return !!mStorage;
}

// https://android.googlesource.com/platform/frameworks/base/+/master/libs/hwui/hwui/Bitmap.cpp
static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) {
    void* addr = calloc(size, 1);
    if (!addr) {
        return nullptr;
    }
    return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes));
}

還是通過BitmapFactory.cpp#doDecode方法來跟蹤,發(fā)現(xiàn)其中tryAllocPixels方法,應(yīng)該是嘗試去進(jìn)行內(nèi)存分配,其中decodeAllocator會(huì)被賦值為HeapAllocator,通過一系列的調(diào)用,最終通過calloc方法,在native分配內(nèi)存。
至于為什么Google 在8.0上改變了Bitmap像素?cái)?shù)據(jù)的存放方式,我猜想和8.0中的GC算法調(diào)整有關(guān)系。GC算法的優(yōu)化,使得Bitmap占用的大內(nèi)存區(qū)域,在GC后也能夠比較快速的回收、壓縮,重新使用。

(native存放) 退出Activity 退出App
onStop中主動(dòng)調(diào)用gc()和recycler() 內(nèi)存不釋放 內(nèi)存釋放
無(wú)調(diào)用 內(nèi)存不釋放 內(nèi)存不釋放
(gpu存放) 退出Activity 退出App
onStop中主動(dòng)調(diào)用gc()和recycler() 內(nèi)存釋放 內(nèi)存釋放
無(wú)調(diào)用 內(nèi)存釋放 內(nèi)存釋放

總結(jié)

// 8.0源碼
    Bitmap(long nativeBitmap, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets)
// 7.0源碼
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets)

一開始看兩者java代碼不同,少了存放像素的buffer字段,查閱相關(guān)資料到native源碼對(duì)比,最終總結(jié)了下Bitmap內(nèi)存相關(guān)的知識(shí)。另外,在Android 8.0中,關(guān)于Bitmap的改動(dòng)有兩方面還需深入探究的:1、Config配置為Hardware時(shí)的優(yōu)劣。Hardware配置實(shí)際上沒有改變像素的位儲(chǔ)存大小(還是默認(rèn)的ARGB8888),但是改變了bitmap像素的存儲(chǔ)位置(存放到GPU內(nèi)存中),對(duì)實(shí)際應(yīng)用的影響會(huì)如何?;2、Bitmap在8.0后又回歸到native存放bitmap像素?cái)?shù)據(jù),而這部分?jǐn)?shù)據(jù)的回收時(shí)機(jī)和觸發(fā)方式又是如何?一般測(cè)試下,可以通過native分配Bitmap超過1G的內(nèi)存數(shù)據(jù)而不發(fā)生崩潰。

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