Android-圖片加載框架(三級緩存)

所謂三級緩存指的是內(nèi)存、磁盤緩存、網(wǎng)絡(luò)加載 烫扼。bitmap獲取存取邏輯大致如下:
1.獲取 url 哀澈,請求網(wǎng)絡(luò)加載牌借。
2.把加載成功的流存入到磁盤(DiskLruCache)
3.流對象轉(zhuǎn)成bitmap對象,并壓縮存入到內(nèi)存中(LruCache)
4.設(shè)置壓縮后的bitmap對象給ImageView
此時圖片已成功顯示并實現(xiàn)存儲割按,再次加載時可依次從內(nèi)存膨报、磁盤中取出bitmap,當(dāng)都為空去請求網(wǎng)絡(luò)并存儲,以此重復(fù)哲虾。

其中涉及到LruCache和DiskLruCache(LRU算法)丙躏,線程池使用,圖片壓縮束凑,Handler解決異步問題晒旅,通過setTag來標(biāo)識ImageView。
關(guān)于:

LruCache

LRU:(Least Recently Used)是近期最少使用算法汪诉,內(nèi)部采用一個LinkedHashMap以強(qiáng)引用的方式存儲外界的緩存對象废恋,核心思想是當(dāng)緩存滿時優(yōu)先淘汰近期最少使用的緩存對象(JakeWharton/DiskLruCache* 下載*)。

        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.getWidth() / 1024;
            }
        };

只需要提供緩存的總?cè)萘看笮〔⒅貙憇izeOf方法扒寄,sizeOf方法的作用是計算緩存對象的大小鱼鼓。特殊情況還需重寫entryRemoved方法,移除舊緩存時會調(diào)用此方法该编,因此可以其中做些資源回收工作(如需要)迄本。

ThreadPoolExecutor

線程池:包括 corePoolSize 核心線程數(shù)、maximumPoolSize 最大線程數(shù)课竣、keepAliveTime 非核心線閑置時的超時時長 嘉赎、unit 時長單位、workQueue 任務(wù)隊列于樟、threadFactory 線程工廠公条,其中線程工廠為線程池提供創(chuàng)建新線程的功能,只有 Thread new Thread(Runnabler)方法迂曲。
執(zhí)行任務(wù)時大致遵循如下規(guī)則:
1)如果線程中的線程數(shù)量 未達(dá)到 核心線程的數(shù)量 靶橱,那么會直接啟動一個核心線程來執(zhí)行任務(wù)。
2)如果 線程中的線程數(shù)量 已經(jīng)達(dá)到或者超過核心線程的數(shù)量 ,那么任務(wù)會被插入到任務(wù)隊列中排除等待執(zhí)行关霸。
3)如果 在步驟2中無法將任務(wù)插入到任務(wù)隊列传黄,這往往是任務(wù)隊列 已滿,這個時候 如果線程數(shù)量未達(dá)到線程池規(guī)定的最大值 谒拴,那么會立刻啟動一個非核心線程來執(zhí)行任務(wù)尝江。
4)如果步驟3中線程數(shù)量已經(jīng)達(dá)到 線程池規(guī)定的最大值 ,那么就拒絕執(zhí)行此任務(wù)英上,
ThreadPoolExecutor 會調(diào)用 RejectedExecutionHandler 的 rejectedExecution 方法來通知調(diào)用者炭序。
線程池的優(yōu)點:
1)重用線程池中的線程,避免因為線程的創(chuàng)建和銷毀所帶來的性能 開銷苍日。
2)能有效控制 線程池的最大并發(fā)數(shù)據(jù)惭聂,避免大量的線程之前 因互相搶占系統(tǒng)資源而導(dǎo)致阻塞的現(xiàn)象。
3)能夠?qū)€程進(jìn)行簡單的管理相恃,并提供定時執(zhí)行以及指定間隔循環(huán)執(zhí)行等功能辜纲。
其實Android封裝了四類線程池,其中FixedThreadPoo只有核心線程并且核心線程不會被回收拦耐,沒有超時機(jī)制耕腾,另外隊列也沒有大小限制 ,所以能更加快速地響應(yīng)外界的請求杀糯;CachedThreadPool適合執(zhí)行大量的耗時較少的任務(wù)扫俺;ScheduledThreadPool適合處理定時和具有固定重復(fù)任務(wù);SingleThreadExcuor內(nèi)部只有一個核心線程固翰,它確保 所有的任務(wù)都 在同一個線程中按順序執(zhí)行狼纬,所以這些任務(wù)之間不需要處理線程同步的問題÷罴剩可以根據(jù)需求選擇合適的線程池疗琉,原理其實就在創(chuàng)建線程池時設(shè)置不同的參數(shù),此處不做詳解歉铝。

圖片壓縮

想要壓縮盈简,我們第一步應(yīng)該是獲得imageview想要顯示的大小,沒大小肯定沒辦法壓縮太示?
那么如何獲得imageview想要顯示的大小呢柠贤?(該模塊轉(zhuǎn)自HongYang)

/**
 * http://blog.csdn.net/lmj623565791/article/details/41874561
 *
 * @author zhy
 */
public class ImageSizeUtil {
    /**
     * 根據(jù)需求的寬和高以及圖片實際的寬和高計算SampleSize
     *
     * @param options
     * @return
     */
    public static int caculateInSampleSize(Options options, int reqWidth,
                                           int reqHeight) {
        int width = options.outWidth;
        int height = options.outHeight;

        int inSampleSize = 1;

        while (width > reqWidth || height > reqHeight) {
            width /= 2;
            height /= 2;
            inSampleSize++;
        }
        return inSampleSize;
    }

    /**
     * 根據(jù)ImageView獲適當(dāng)?shù)膲嚎s的寬和高
     *
     * @param imageView
     * @return
     */
    public static ImageSize getImageViewSize(ImageView imageView) {

        ImageSize imageSize = new ImageSize();
        DisplayMetrics displayMetrics = imageView.getContext().getResources()
                .getDisplayMetrics();


        LayoutParams lp = imageView.getLayoutParams();

        int width = imageView.getWidth();// 獲取imageview的實際寬度
        if (width <= 0) {
            width = lp.width;// 獲取imageview在layout中聲明的寬度
        }
        if (width <= 0) {
            //width = imageView.getMaxWidth();// 檢查最大值
            width = getImageViewFieldValue(imageView, "mMaxWidth");
        }
        if (width <= 0) {
            width = displayMetrics.widthPixels;
        }

        int height = imageView.getHeight();// 獲取imageview的實際高度
        if (height <= 0) {
            height = lp.height;// 獲取imageview在layout中聲明的寬度
        }
        if (height <= 0) {
            height = getImageViewFieldValue(imageView, "mMaxHeight");// 檢查最大值
        }
        if (height <= 0) {
            height = displayMetrics.heightPixels;
        }
        imageSize.width = width;
        imageSize.height = height;

        return imageSize;
    }

    public static class ImageSize {
        int width;
        int height;
    }

    /**
     * 通過反射獲取imageview的某個屬性值
     *
     * @param object
     * @param fieldName
     * @return
     */
    public static int getImageViewFieldValue(Object object, String fieldName) {
        int value = 0;
        try {
            Field field = ImageView.class.getDeclaredField(fieldName);
            field.setAccessible(true);
            int fieldValue = field.getInt(object);
            if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
                value = fieldValue;
            }
        } catch (Exception e) {
        }
        return value;
    }
}

可以看到,我們拿到imageview以后:
首先企圖通過getWidth獲取顯示的寬先匪;有些時候,這個getWidth返回的是0弃衍;
那么我們再去看看它有沒有在布局文件中書寫寬呀非;
如果布局文件中也沒有精確值,那么我們再去看看它有沒有設(shè)置最大值;
如果最大值也沒設(shè)置岸裙,那么我們只有拿出我們的終極方案猖败,使用我們的屏幕寬度;
總之降允,不能讓它任性恩闻,我們一定要拿到一個合適的顯示值。
可以看到這里或者最大寬度剧董,我們用的反射幢尚,而不是getMaxWidth();因為getMaxWidth竟然要API 16翅楼;為了兼容性尉剩,我們采用反射的方案。
得到尺寸大小后可通過caculateInSampleSize()方法設(shè)置合適的inSampleSize毅臊。

好吧理茎,代碼給了很詳盡的注釋,具體實現(xiàn)可根據(jù)代碼看更加清晰管嬉。
完整代碼如下 :

/**
 * Created by zhanFeng on 2017/3/22.
 */

public class ImageLoader {

    private static ImageLoader sImageLoader;

    public static ImageLoader getInstance(Context context) {
        if (sImageLoader == null) {
            synchronized (ImageLoader.class) {
                if (sImageLoader == null) {
                    sImageLoader = new ImageLoader(context);
                }
            }
        }
        return sImageLoader;
    }

    private static final String TAG = "ImageLoader";
    private Context mContext;
    private LruCache<String, Bitmap> mMemoryCache;
    private DiskLruCache mDiskLruCache;
    //磁盤緩存空間 20M
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 20;
    private boolean mIsDiskLruCacheCreated = false;

    private final static int TAG_KEY_URI = R.id.imageLoader_uri;
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXMUN_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final long KEEP_ALIVE = 10L;

    private static final ThreadFactory sThreadFactor = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
        }
    };

    //創(chuàng)建線程池
    private static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXMUN_POOL_SIZE,
            KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), sThreadFactor);

    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoaderResult loaderResult = (LoaderResult) msg.obj;
            ImageView imageView = loaderResult.mImageView;
            String uri = (String) imageView.getTag(TAG_KEY_URI);
            if (uri.equals(loaderResult.uri)) {
                System.out.println("Handler:" + uri);
                imageView.setImageBitmap(loaderResult.bitmap);
            } else {
                Log.e(TAG, "set image bitmap,but url has changed,ignored!");
            }
        }
    };

    private ImageLoader(Context context) {
        //單例中防止上下文引起的內(nèi)存泄漏
        mContext = context.getApplicationContext();
        //獲取應(yīng)用內(nèi)存
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //內(nèi)存緩存空間為應(yīng)用內(nèi)存的1/8
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getWidth() / 1024;
            }
        };

        //創(chuàng)建保存bitmap的文件
        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
        //獲取手機(jī)存儲空間大小,當(dāng)空間允許時創(chuàng)建本地緩存對象
        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
                //創(chuàng)建成功時設(shè)置標(biāo)記
                mIsDiskLruCacheCreated = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 獲取手機(jī)存儲空間大小
     *
     * @param path
     * @return
     */
    private long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        final StatFs statFs = new StatFs(path.getPath());
        return statFs.getBlockSizeLong() * statFs.getAvailableBlocksLong();
    }

    /**
     * 創(chuàng)建保存bitmap的文件
     * true 有SD卡且未移除時創(chuàng)建在SD卡創(chuàng)建      false 創(chuàng)建在內(nèi)置存儲卡上
     *
     * @param context
     * @param bitmapName
     * @return
     */
    private File getDiskCacheDir(Context context, String bitmapName) {
        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 + bitmapName);
    }

    /**
     * 從內(nèi)存中獲取bitmap
     *
     * @param url
     * @return
     */
    private Bitmap loadBitmapFromMemCache(String url) {
        String key = hashKeyFormUrl(url);
        return mMemoryCache.get(key);
    }

    /**
     * 添加bitmap到內(nèi)存中
     *
     * @param url
     * @param bitmap
     */
    private void addBitmapToMemoryCache(String url, Bitmap bitmap) {
        if (loadBitmapFromMemCache(url) == null) {
            if (bitmap != null) {
                mMemoryCache.put(url, bitmap);
            }
        }
    }

    public void displayImage(@Nullable final String url, final ImageView imageView) {
        //setTag 與 Handler中g(shù)etTag可對imageView進(jìn)行識別皂林,避免異步造成圖片顯示錯位
        imageView.setTag(TAG_KEY_URI, url);

        Bitmap bitmap = loadBitmapFromMemCache(url);

        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        //創(chuàng)建任務(wù)
        Runnable runnableTask = new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(url, imageView);
                if (bitmap != null) {
                    LoaderResult loaderResult = new LoaderResult(imageView, url, bitmap);
                    //Handler發(fā)送消息
                    mMainHandler.obtainMessage(0, loaderResult).sendToTarget();

                }
            }
        };

        //將任務(wù)放入線程池執(zhí)行
        THREAD_POOL_EXECUTOR.execute(runnableTask);
    }

    /**
     * @param url
     * @param imageView
     * @return
     */
    private Bitmap loadBitmap(String url, ImageView imageView) {
        Bitmap bitmap = null;

        bitmap = loadBitmapFromMemCache(url);
        if (bitmap != null) {
            return bitmap;
        }
        try {
            bitmap = loadBitmapFromDiskCache(url, imageView);
            if (bitmap != null) {
                return bitmap;
            }

            bitmap = loadBitmapFromHttp(url, imageView);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //存儲空間不夠大時未創(chuàng)建磁盤緩存,直接從網(wǎng)絡(luò)加載
        if (bitmap == null && !mIsDiskLruCacheCreated) {
            bitmap = downloadBitmapFromUrl(url);
        }

        return bitmap;
    }

    /**
     * 從磁盤中加載bitmap
     *
     * @param url
     * @param imageView
     * @return bitmap
     * @throws IOException
     */
    private Bitmap loadBitmapFromDiskCache(String url, ImageView imageView) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "load bitmap from UI Thread,it's not recommend!");
        }
        if (mDiskLruCache == null) {
            return null;
        }
        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
        if (snapshot != null) {
            FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(0);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            ImageSizeUtil.ImageSize imageViewSize = ImageSizeUtil.getImageViewSize(imageView);
            //將文件描述符壓縮并轉(zhuǎn)換成bitmap
            bitmap = decodeSampledBitmapFromPath(fileDescriptor, imageViewSize.width, imageViewSize.height);
            if (null != bitmap) {
                //保存到內(nèi)存中
                addBitmapToMemoryCache(key, bitmap);
            }
        }
        return bitmap;
    }

    private static class LoaderResult {
        private ImageView mImageView;
        private String uri;
        private Bitmap bitmap;

        public LoaderResult(ImageView imageView, String url, Bitmap bitmap) {
            this.mImageView = imageView;
            this.uri = url;
            this.bitmap = bitmap;
        }

    }


    public static final int IO_BUFFER_SIZE = 1024 * 8;

    private Bitmap downloadBitmapFromUrl(String uri) {

        Bitmap bitmap = null;
        HttpURLConnection httpURLConnection = null;
        BufferedInputStream is = null;

        try {
            URL url = new URL(uri);
            try {
                httpURLConnection = (HttpURLConnection) url.openConnection();
                is = new BufferedInputStream(httpURLConnection.getInputStream(), IO_BUFFER_SIZE);
                //此處可把bitmap對象進(jìn)行壓縮蚯撩,可自行實現(xiàn)础倍,很簡單的
                bitmap = BitmapFactory.decodeStream(is);
            } catch (IOException e) {
                Log.e(TAG, "Error in downloadBitmap:" + e);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != httpURLConnection) {
                try {
                    httpURLConnection.disconnect();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }

    /**
     * 從網(wǎng)絡(luò)下載圖片
     *
     * @param url
     * @param imageView
     * @return
     */
    private Bitmap loadBitmapFromHttp(String url, ImageView imageView) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network form UI Thread");
        }
        if (mDiskLruCache == null) {
            return null;
        }
        String key = hashKeyFormUrl(url);
        try {
            DiskLruCache.Editor edit = mDiskLruCache.edit(key);
            if (null != edit) {
                OutputStream outputStream = edit.newOutputStream(0);
                if (downloadUrlToStream(url, outputStream)) {
                    edit.commit();
                } else {
                    edit.abort();
                }
                mDiskLruCache.flush();
            }
            return loadBitmapFromDiskCache(url, imageView);
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 從網(wǎng)絡(luò)加載圖片并保存到磁盤中
     *
     * @param urlString
     * @param outputStream
     * @return
     */
    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * md5加密,避免url存在特殊字符
     *
     * @param url
     * @return
     */
    private String hashKeyFormUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] digest) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < digest.length; i++) {
            String hex = Integer.toHexString(0xFF & digest[i]);
            if (hex.length() == 1) {
                stringBuilder.append('0');
            }
            stringBuilder.append(hex);
        }
        return stringBuilder.toString();
    }

    /**
     * 根據(jù)圖片需要顯示的寬和高對圖片進(jìn)行壓縮
     *
     * @param fileDescriptor
     * @param width
     * @param height
     * @return
     */
    protected Bitmap decodeSampledBitmapFromPath(FileDescriptor fileDescriptor, int width, int height) {
        // 獲得圖片的寬和高求厕,并不把圖片加載到內(nèi)存中
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        //根據(jù)需求的寬和高以及圖片實際的寬和高計算SampleSize
        options.inSampleSize = ImageSizeUtil.caculateInSampleSize(options,
                width, height);

        // 使用獲得到的InSampleSize再次解析圖片
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        return bitmap;

    }
}

好吧著隆,終于自己實現(xiàn)了圖片加載,設(shè)置屬性可通過builder模式來進(jìn)行封裝呀癣。當(dāng)然美浦,其中遇到一個坑就是MD5加密時,相應(yīng)位置替換成mDigest.digest().length和mDigest.digest().[i]會出現(xiàn)不同的url轉(zhuǎn)化的key相同的問題项栏,此處還得對mDigest.digest()轉(zhuǎn)化成一個數(shù)組對象再進(jìn)行調(diào)用浦辨!


參考:
《Abdroid開發(fā)藝術(shù)探索》
Android 框架練成 教你打造高效的圖片加載框架
Android照片墻完整版,完美結(jié)合LruCache和DiskLruCache

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沼沈,一起剝皮案震驚了整個濱河市流酬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌列另,老刑警劉巖芽腾,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異页衙,居然都是意外死亡摊滔,警方通過查閱死者的電腦和手機(jī)阴绢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艰躺,“玉大人呻袭,你說我怎么就攤上這事∠傩耍” “怎么了左电?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長页响。 經(jīng)常有香客問我篓足,道長,這世上最難降的妖魔是什么拘泞? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任纷纫,我火速辦了婚禮,結(jié)果婚禮上陪腌,老公的妹妹穿的比我還像新娘辱魁。我一直安慰自己,他們只是感情好诗鸭,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布染簇。 她就那樣靜靜地躺著,像睡著了一般强岸。 火紅的嫁衣襯著肌膚如雪锻弓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天蝌箍,我揣著相機(jī)與錄音青灼,去河邊找鬼。 笑死妓盲,一個胖子當(dāng)著我的面吹牛杂拨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播悯衬,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼弹沽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了筋粗?” 一聲冷哼從身側(cè)響起策橘,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娜亿,沒想到半個月后丽已,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡买决,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年沛婴,在試婚紗的時候發(fā)現(xiàn)自己被綠了辰斋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘸味,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出够挂,到底是詐尸還是另有隱情旁仿,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布孽糖,位于F島的核電站枯冈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏办悟。R本人自食惡果不足惜尘奏,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望病蛉。 院中可真熱鬧炫加,春花似錦、人聲如沸铺然。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魄健。三九已至赋铝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沽瘦,已是汗流浹背革骨。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留析恋,地道東北人良哲。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像绿满,于是被迫代替她去往敵國和親臂外。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

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