Android Bitmap的加載和Cache

導(dǎo)語(yǔ)

主要介紹如何高效地加載一個(gè)Bitmap,Android中常用的緩存策略枪萄,如何優(yōu)化列表的卡頓。

主要內(nèi)容

  • Bitmap的高效加載
  • Android中的緩存策略
  • ImageLoader的使用

具體內(nèi)容

Bitmap的高效加載

先來(lái)簡(jiǎn)單介紹一下如何加載一個(gè)Bitmap, Bitmap在android中指的是一張圖片, 可以是png格式也可以是jpg等其他常見(jiàn)的圖片格式.

那么如何加載一個(gè)圖片?首先BitmapFactory類(lèi)提供了四種方法: decodeFile(), decodeResource(), decodeStream(), decodeByteArray(). 分別用于從文件系統(tǒng), 資源文件, 輸入流以及字節(jié)數(shù)組加載出一個(gè)Bitmap對(duì)象. 其中decodeFile和decodeResource又間接調(diào)用了decodeStream()方法, 這四類(lèi)方法最終是在Android的底層實(shí)現(xiàn)的, 對(duì)應(yīng)著B(niǎo)itmapFactory類(lèi)的幾個(gè)native方法.

高效加載的Bitmap的核心思想:采用BitmapFactory.Options來(lái)加載所需尺寸的圖片. 比如說(shuō)一個(gè)ImageView控件的大小為300300. 而圖片的大小為800800. 這個(gè)時(shí)候如果直接加載那么就比較浪費(fèi)資源, 需要更多的內(nèi)存空間來(lái)加載圖片, 這不是很必要的. 這里我們就可以先把圖片按一定的采樣率來(lái)縮小圖片在進(jìn)行加載. 不僅降低了內(nèi)存占用,還在一定程度上避免了OOM異常. 也提高了加載bitmap時(shí)的性能.

而通過(guò)Options參數(shù)來(lái)縮放圖片: 主要是用到了inSampleSize參數(shù), 即采樣率。

  • 如果是inSampleSize=1那么和原圖大小一樣,
  • 如果是inSampleSize=2那么寬高都為原圖1/2, 而像素為原圖的1/4, 占用的內(nèi)存大小也為原圖的1/4
  • 如果是inSampleSize=3那么寬高都為原圖1/3, 而像素為原圖的1/9, 占用的內(nèi)存大小也為原圖的1/9
  • 以此類(lèi)推…..

要知道Android中加載圖片具體在內(nèi)存中的占有的大小是根據(jù)圖片的像素決定的, 而與圖片的實(shí)際占用空間大小沒(méi)有關(guān)系.而且如果要加載mipmap下的圖片, 還會(huì)根據(jù)不同的分辨率下的文件夾進(jìn)行不同的放大縮小.

列舉現(xiàn)在有一張圖片像素為:10241024, 如果采用ARGB8888(四個(gè)顏色通道每個(gè)占有一個(gè)字節(jié),相當(dāng)于1點(diǎn)像素占用4個(gè)字節(jié)的空間)的格式來(lái)存儲(chǔ).(這里不考慮不同的資源文件下情況分析) 那么圖片的占有大小就是102410244那現(xiàn)在這張圖片在內(nèi)存中占用4MB.
如果針對(duì)剛才的圖片進(jìn)行inSampleSize=2, 那么最后占用內(nèi)存大小為512512*4, 也就是1MB

采樣率的數(shù)值必須是大于1的整數(shù)是才會(huì)有縮放效果, 并且采樣率同時(shí)作用于寬/高, 這將導(dǎo)致縮放后的圖片以這個(gè)采樣率的2次方遞減, 即內(nèi)存占用縮放大小為1/(inSampleSize的二次方). 如果小于1那么相當(dāng)于=1的時(shí)候. 在官方文檔中指出, inSampleSize的取值應(yīng)該總是為2的指數(shù), 比如1,2,4,8,16,32…如果外界傳遞inSampleSize不為2的指數(shù), 那么系統(tǒng)會(huì)向下取整并選擇一個(gè)最接近的2的指數(shù)來(lái)代替. 比如如果inSampleSize=3,那么系統(tǒng)會(huì)選擇2來(lái)代替. 但是這條規(guī)則并不作用于所有的android版本, 所以可以當(dāng)成一個(gè)開(kāi)發(fā)建議

整理一下開(kāi)發(fā)中代碼流程:

  1. 將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)置為true并加載圖片扔役。
  2. 從BitmapFactory.Options取出圖片的原始寬高信息, 他們對(duì)應(yīng)于outWidth和outHeight參數(shù)肄鸽。
  3. 根據(jù)采樣率的規(guī)則并結(jié)合目標(biāo)View的所需大小計(jì)算出采樣率inSampleSize卫病。
  4. 將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)為false, 然后重新加載。

inJustDecodeBounds這個(gè)參數(shù)的作用就是在加載圖片的時(shí)候是否只是加載圖片寬高信息而不把圖片全部加載到內(nèi)存. 所以這個(gè)操作是個(gè)輕量級(jí)的.

通過(guò)這些步驟就可以整理出以下的工具加載圖片類(lèi)調(diào)用decodeFixedSizeForResource()即可.

public class MyBitmapLoadUtil {
    /**
     * 對(duì)一個(gè)Resources的資源文件進(jìn)行指定長(zhǎng)寬來(lái)加載進(jìn)內(nèi)存, 并把這個(gè)bitmap對(duì)象返回
     *
     * @param res   資源文件對(duì)象
     * @param resId 要操作的圖片id
     * @param reqWidth 最終想要得到bitmap的寬度
     * @param reqHeight 最終想要得到bitmap的高度
     * @return 返回采樣之后的bitmap對(duì)象
     */
    public static Bitmap decodeFixedSizeForResource(Resources res, int resId, int reqWidth, int reqHeight){
        // 首先先指定加載的模式 為只是獲取資源文件的大小
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        //Calculate Size  計(jì)算要設(shè)置的采樣率 并把值設(shè)置到option上
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        // 關(guān)閉只加載屬性模式, 并重新加載的時(shí)候傳入自定義的options對(duì)象
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }
    /**
     *  一個(gè)計(jì)算工具類(lèi)的方法, 傳入圖片的屬性對(duì)象和 想要實(shí)現(xiàn)的目標(biāo)大小. 通過(guò)計(jì)算得到采樣值
     */
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        //Raw height and width of image
        //原始圖片的寬高屬性
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        // 如果想要實(shí)現(xiàn)的寬高比原始圖片的寬高小那么就可以計(jì)算出采樣率, 否則不需要改變采樣率
        if (reqWidth < height || reqHeight < width){
            int halfWidth = width/2;
            int halfHeight = height/2;
            // 判斷原始長(zhǎng)寬的一半是否比目標(biāo)大小小, 如果小那么增大采樣率2倍, 直到出現(xiàn)修改后原始值會(huì)比目標(biāo)值大的時(shí)候
            while((halfHeight/inSampleSize) >= reqHeight && (halfWidth/inSampleSize) >= reqWidth){
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

Android中的緩存策略

當(dāng)程序第一次從網(wǎng)絡(luò)上加載圖片后典徘,將其緩存在存儲(chǔ)設(shè)備中蟀苛,下次使用這張圖片的時(shí)候就不用再?gòu)木W(wǎng)絡(luò)從獲取了慧妄。很多時(shí)候?yàn)榱颂岣邞?yīng)用的用戶體驗(yàn)初澎,往往還會(huì)把圖片在內(nèi)存中再緩存一份,因?yàn)閺膬?nèi)存中加載圖片比存儲(chǔ)設(shè)備中快序仙。一般情況會(huì)把圖片存一份到內(nèi)存中梅鹦,一份到存儲(chǔ)設(shè)備中裆甩,如果內(nèi)存中沒(méi)找到就去存儲(chǔ)設(shè)備中找,還沒(méi)有找到就從網(wǎng)絡(luò)上下載齐唆。

緩存策略包含緩存的添加嗤栓、獲取和刪除操作。不管是內(nèi)存還是存儲(chǔ)設(shè)備箍邮,緩存大小都是有限制的茉帅。如何刪除舊的緩存并添加新的緩存,就對(duì)應(yīng)緩存算法锭弊。

目前常用的一種緩存算法是LRU(Least Recently Used), 最近最少使用算法. 核心思想: 當(dāng)緩存存滿時(shí), 會(huì)優(yōu)先淘汰那些近期最少使用的緩存對(duì)象. 采用LRU算法的緩存有兩種: LruCache和DiskLruCache,LruCahe用于實(shí)現(xiàn)內(nèi)存緩存, DiskLruCache則充當(dāng)了存儲(chǔ)設(shè)備緩存, 當(dāng)組合使用后就可以實(shí)現(xiàn)一個(gè)類(lèi)似ImageLoader這樣的類(lèi)庫(kù).

LruCache

LruCache是Android 3.1所提供的一個(gè)緩存類(lèi), 通過(guò)support-v4兼容包可以兼容到早期的Android版本

LruCache是一個(gè)泛型類(lèi), 它內(nèi)部采用了一個(gè)LinkedHashMap以強(qiáng)引用的方式存儲(chǔ)外界的緩存對(duì)象, 其提供了get和put方法來(lái)完成緩存的獲取和添加的操作. 當(dāng)緩存滿了時(shí), LruCache會(huì)移除較早使用的緩存對(duì)象, 然后在添加新的緩存對(duì)象. 普及一下各種引用的區(qū)別:

  • 強(qiáng)引用: 直接的對(duì)象引用
  • 軟引用: 當(dāng)一個(gè)對(duì)象只有軟引用存在時(shí), 系統(tǒng)內(nèi)存不足時(shí)此對(duì)象會(huì)被gc回收
  • 弱引用: 當(dāng)一個(gè)對(duì)象只有弱引用存在時(shí), 對(duì)象會(huì)隨下一次gc時(shí)被回收

LruCache是線程安全的堪澎。
LruCache 典型初始化過(guò)程:

    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    int cacheSize = maxMemory / 8;
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap value) {
            return value.getRowBytes() * value.getHeight() / 1024;
        }
    };

這里只需要提供緩存的總?cè)萘看笮?一般為進(jìn)程可用內(nèi)存的1/8)并重寫(xiě) sizeOf 方法即可.sizeOf方法作用是計(jì)算緩存對(duì)象的大小。這里大小的單位需要和總?cè)萘康膯挝唬ㄟ@里是kb)一致味滞,因此除以1024樱蛤。一些特殊情況下,需要重寫(xiě)LruCache的entryRemoved方法剑鞍,LruCache移除舊緩存時(shí)會(huì)調(diào)用entryRemoved方法昨凡,因此可以在entryRemoved中完成一些資源回收工作(如果需要的話)。

還有獲取和添加方法攒暇,都比較簡(jiǎn)單:

mMemoryCache.get(key)
mMemoryCache.put(key,bitmap)

通過(guò)remove方法可以刪除一個(gè)指定的對(duì)象土匀。

從Android 3.1開(kāi)始,LruCache稱(chēng)為Android源碼的一部分形用。

DiskLruCache

DiskLruCache用于實(shí)現(xiàn)磁盤(pán)緩存就轧,DiskLruCache得到了Android官方文檔推薦证杭,但它不屬于Android SDK的一部分,源碼在這里妒御。

DiskLruCache的創(chuàng)建:
DiskLruCache并不能通過(guò)構(gòu)造方法來(lái)創(chuàng)建, 他提供了open()方法用于創(chuàng)建自身, 如下所示

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
  • File directory: 表示磁盤(pán)緩存在文件系統(tǒng)中的存儲(chǔ)路徑. 可以選擇SD卡上的緩存目錄, 具體是指/sdcard/Andriod/data/package_name/cache目錄, package_name表示當(dāng)前應(yīng)用的包名, 當(dāng)應(yīng)用被卸載后, 此目錄會(huì)一并刪除掉. 也可以選擇data目錄下. 或者其他地方. 這里給出的建議:如果應(yīng)用卸載后就希望刪除緩存文件的話 , 那么就選擇SD卡上的緩存目錄, 如果希望保留緩存數(shù)據(jù)那就應(yīng)該選擇SD卡上的其他目錄.
  • int appVersion: 表示應(yīng)用的版本號(hào), 一般設(shè)為1即可. 當(dāng)版本號(hào)發(fā)生改變的時(shí)候DiskLruCache會(huì)清空之前所有的緩存文件, 在實(shí)際開(kāi)發(fā)中這個(gè)實(shí)用性不大.
  • int valueCount: 表示單個(gè)節(jié)點(diǎn)所對(duì)應(yīng)的數(shù)據(jù)的個(gè)數(shù), 一般設(shè)為1.
  • long maxSize: 表示緩存的總大小, 比如50MB, 當(dāng)緩存大小超出這個(gè)設(shè)定值后, DiskLruCache會(huì)清除一些緩存而保證總大小不大于這個(gè)設(shè)定值.
    //初始化DiskLruCache解愤,包括一些參數(shù)的設(shè)置
    public void initDiskLruCache
    {
        //配置固定參數(shù)
        // 緩存空間大小
        private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
        //下載圖片時(shí)的緩存大小
        private static final long IO_BUFFER_SIZE = 1024 * 8;
        // 緩存空間索引,用于Editor和Snapshot乎莉,設(shè)置成0表示Entry下面的第一個(gè)文件
        private static final int DISK_CACHE_INDEX = 0;
 
        //設(shè)置緩存目錄
        File diskLruCache = getDiskCacheDir(mContext, "bitmap");
        if(!diskLruCache.exists())
            diskLruCache.mkdirs();
        //創(chuàng)建DiskLruCache對(duì)象送讲,當(dāng)然是在空間足夠的情況下
        if(getUsableSpace(diskLruCache) > DISK_CACHE_SIZE)
        {
            try
            {
                mDiskLruCache = DiskLruCache.open(diskLruCache, 
                        getAppVersion(mContext), 1, DISK_CACHE_SIZE);
                mIsDiskLruCache = true;
            }catch(IOException e)
            {
                e.printStackTrace();
            }
        }
    }
 
    //上面的初始化過(guò)程總共用了3個(gè)方法
    //設(shè)置緩存目錄
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }
 
    // 獲取可用的存儲(chǔ)大小
    @TargetApi(VERSION_CODES.GINGERBREAD)
    private long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD)
            return path.getUsableSpace();
        final StatFs stats = new StatFs(path.getPath());
        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
    }
    //獲取應(yīng)用版本號(hào),注意不同的版本號(hào)會(huì)清空緩存
    public int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(
                    context.getPackageName(), 0);
            return info.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

DiskLruCache的緩存添加:
DiskLruCache的緩存添加的操作是通過(guò)Editor完成的, Editor表示一個(gè)緩存對(duì)象的編輯對(duì)象.

如果還是緩存圖片為例子, 每一張圖片都通過(guò)圖片的url為key, 這里由于url可能會(huì)有特殊字符所以采用url的md5值作為key. 根據(jù)這個(gè)key就可以通過(guò)edit()來(lái)獲取Editor對(duì)象, 如果這個(gè)緩存對(duì)象正在被編輯, 那么edit()就會(huì)返回null. 即DiskLruCache不允許同時(shí)編輯一個(gè)緩存對(duì)象.

當(dāng)用.edit(key)獲得了Editor對(duì)象之后. 通過(guò)editor.newOutputStream(0)就可以得到一個(gè)文件輸出流. 由于之前open()方法設(shè)置了一個(gè)節(jié)點(diǎn)只能有一個(gè)數(shù)據(jù). 所以在獲得輸出流的時(shí)候傳入常量0即可.

有了文件輸出流, 可以當(dāng)網(wǎng)絡(luò)下載圖片時(shí), 圖片就可以通過(guò)這個(gè)文件輸出流寫(xiě)入到文件系統(tǒng)上.最后惋啃,要通過(guò)Editor中commit()來(lái)提交寫(xiě)操作, 如果下載中發(fā)生異常, 那么使用Editor中abort()來(lái)回退整個(gè)操作.

DiskLruCache的緩存查找:
和緩存的添加過(guò)程類(lèi)似, 緩存查找過(guò)程也需要將url轉(zhuǎn)換成key, 然后通過(guò)DiskLruCache#get()方法可以得到一個(gè)Snapshot對(duì)象, 接著在通過(guò)Snapshot對(duì)象即可得到緩存的文件輸入流, 有了文件輸入流, 自然就可以得到Bitmap對(duì)象. 為了避免加載圖片出現(xiàn)OOM所以采用壓縮的方式. 在前面對(duì)BitmapFactory.Options的使用說(shuō)明了. 但是這中方法對(duì)FileInputStream的縮放存在問(wèn)題. 原因是FileInputStream是一種有序的文件流, 而兩次decodeStream調(diào)用會(huì)影響文件的位置屬性, 這樣在第二次decodeStream的時(shí)候得到的會(huì)是null. 針對(duì)這一個(gè)問(wèn)題, 可以通過(guò)文件流來(lái)得到它所對(duì)應(yīng)的文件描述符, 然后通過(guò)BitmapFactory.decodeFileDescription()來(lái)加載一張縮放后的圖片.

/**
     * 磁盤(pán)緩存的讀取
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
 */
private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException
{
    if(Looper.myLooper() == Looper.getMainLooper())
        Log.w(TAG, "it's not recommented load bitmap from UI Thread");
    if(mDiskLruCache == null)
        return null;
 
    Bitmap bitmap = null;
    String key = hashKeyForDisk(url);
    Snapshot snapshot = mDiskLruCache.get(key);
    if(snapshot != null)
    {
        FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
        FileDescriptor fd = fileInputStream.getFD();
        bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(fd, reqWidth, reqHeight);
 
        if(bitmap != null)
            addBitmapToMemoryCache(key, bitmap);
 
    }
    return bitmap;      
}
ImageLoader的實(shí)現(xiàn)

一個(gè)好的ImageLoader應(yīng)該具備以下幾點(diǎn):

  • 圖片的壓縮
  • 網(wǎng)絡(luò)拉取
  • 內(nèi)存緩存
  • 磁盤(pán)緩存
  • 圖片的同步加載
  • 圖片的異步加載

圖片壓縮功能
ImageResizer
內(nèi)存緩存和磁盤(pán)緩存
ImageLoader
同步加載和異步加載的接口設(shè)計(jì)
ImageLoader 173行

異步加載過(guò)程:

  1. bindBitmap先嘗試從內(nèi)存緩存讀取圖片哼鬓,如果沒(méi)有會(huì)在線程池中調(diào)用loadBitmap方法。獲取成功將圖片封裝為L(zhǎng)oadResult對(duì)象通過(guò)mMainHandler向UI線程發(fā)送消息边灭。選擇線程池和Handler來(lái)提供并發(fā)能力和異步能力异希。
  2. 為了解決View復(fù)用導(dǎo)致的列表錯(cuò)位問(wèn)題,在給ImageView設(shè)置圖片之前都會(huì)檢查它的url有沒(méi)有發(fā)生改變绒瘦,如果改變就不再給它設(shè)置圖片称簿。(76行)

ImageLoader的使用

照片墻效果

實(shí)現(xiàn)照片墻效果,如果圖片都需要是正方形惰帽;這樣做很快憨降,自定義一個(gè)ImageView,重寫(xiě)onMeasure方法该酗。

@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
    super.onMeasure(widthMeasureSpec,widthMeasureSpec);//將原來(lái)的參數(shù)heightMeasureSpec換成widthMeasureSpec
}
優(yōu)化列表的卡頓現(xiàn)象
  1. 不要在getView中執(zhí)行耗時(shí)操作授药,不要在getView中直接加載圖片。
  2. 控制異步任務(wù)的執(zhí)行頻率:如果用戶刻意頻繁上下滑動(dòng)垂涯,getView方法會(huì)不停調(diào)用烁焙,從而產(chǎn)生大量的異步任務(wù)航邢「福可以考慮在列表滑動(dòng)停止加載圖片;給ListView或者GridView設(shè)置 setOnScrollListener 并在 OnScrollListener 的 onScrollStateChanged 方法中判斷列表是否處于滑動(dòng)狀態(tài)膳殷,如果是的話就停止加載圖片操骡。
  3. 大部分情況下,可以使用硬件加速解決莫名卡頓問(wèn)題赚窃,通過(guò)設(shè)置 android:hardwareAccelerated=”true” 即可為Activity開(kāi)啟硬件加速册招。

本章內(nèi)容更多參考

更多內(nèi)容戳這里(整理好的各種文集)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市勒极,隨后出現(xiàn)的幾起案子是掰,更是在濱河造成了極大的恐慌,老刑警劉巖辱匿,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件键痛,死亡現(xiàn)場(chǎng)離奇詭異炫彩,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)絮短,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)江兢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人丁频,你說(shuō)我怎么就攤上這事杉允。” “怎么了席里?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵叔磷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我奖磁,道長(zhǎng)世澜,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任署穗,我火速辦了婚禮寥裂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘案疲。我一直安慰自己封恰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布褐啡。 她就那樣靜靜地躺著诺舔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪备畦。 梳的紋絲不亂的頭發(fā)上低飒,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音懂盐,去河邊找鬼褥赊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛莉恼,可吹牛的內(nèi)容都是我干的拌喉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼俐银,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼尿背!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起捶惜,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤田藐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體汽久,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茴晋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了回窘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诺擅。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖啡直,靈堂內(nèi)的尸體忽然破棺而出烁涌,到底是詐尸還是另有隱情,我是刑警寧澤酒觅,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布撮执,位于F島的核電站,受9級(jí)特大地震影響舷丹,放射性物質(zhì)發(fā)生泄漏抒钱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一颜凯、第九天 我趴在偏房一處隱蔽的房頂上張望谋币。 院中可真熱鬧,春花似錦症概、人聲如沸蕾额。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)诅蝶。三九已至,卻和暖如春募壕,著一層夾襖步出監(jiān)牢的瞬間调炬,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工舱馅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缰泡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓习柠,卻偏偏與公主長(zhǎng)得像匀谣,于是被迫代替她去往敵國(guó)和親照棋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子资溃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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