SharedPreferences存取數(shù)據(jù)流程分析

SharedPreferences存取數(shù)據(jù)流程分析

SharedPreferencesImpl

今天研究一下SharedPreferences存取數(shù)據(jù)的實(shí)現(xiàn)兽埃,在Android中SharedPreferences是一個(gè)接口,真正的實(shí)現(xiàn)類是SharedPreferencesImpl魁兼,下面就開始分析SharedPreferencesImpl典徊,首先查看相關(guān)的屬性

private final File mFile;// 存放數(shù)據(jù)的文件
private final File mBackupFile;// 更新數(shù)據(jù)時(shí)的備份文件
private final int mMode; //sharedPreferences的模式
private final Object mLock = new Object();// 鎖
private final Object mWritingToDiskLock = new Object(); // 寫入磁盤的鎖
private Map<String, Object> mMap; // 存放的數(shù)據(jù)映射
private int mDiskWritesInFlight = 0; // 排隊(duì)寫入磁盤的任務(wù)數(shù)
private long mDiskStateGeneration; // 最后一次提交寫入磁盤的state颤专,自增的
private long mCurrentMemoryStateGeneration; // 當(dāng)前內(nèi)存數(shù)據(jù)的state, 自增的
private boolean mLoaded = false; // 是否已經(jīng)在加載文件的數(shù)據(jù)

下面在看下構(gòu)造函數(shù)

SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file); //構(gòu)造出備份文件
    mMode = mode;
    mLoaded = false;//置為false
    mMap = null; 
    mThrowable = null;
    startLoadFromDisk(); //開始加載文件數(shù)據(jù)
}

//開個(gè)線程做加載數(shù)據(jù)的操作
private void startLoadFromDisk() {
    synchronized (mLock) {
    // mLoaded置為false
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}

//構(gòu)造個(gè)備份文件
static File makeBackupFile(File prefsFile) {
    return new File(prefsFile.getPath() + ".bak");
}

下面再看下真正加載數(shù)據(jù)的方法逮光,loadFromDisk()

private void loadFromDisk() {
    synchronized (mLock) {
    // 判斷是否已經(jīng)加載過(guò)了代箭,不重復(fù)加載
        if (mLoaded) {
            return;
        }
        // 如果備份文件存在,則說(shuō)明上次 創(chuàng)建/更新 操作中涕刚,寫入新的數(shù)據(jù)過(guò)程中發(fā)生了異常嗡综,此時(shí)繼續(xù)采用備份文件的數(shù)據(jù),放棄上次操作
        if (mBackupFile.exists()) {
            mFile.delete();// 刪除新文件
            mBackupFile.renameTo(mFile);
        }
    }

    // Debugging
    if (mFile.exists() && !mFile.canRead()) {
        Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
    }

    Map<String, Object> map = null;
    StructStat stat = null;
    Throwable thrown = null;
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
            // 構(gòu)造存放文件的數(shù)據(jù)流杜漠,從mFile讀數(shù)據(jù)
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16 * 1024);
                        // 讀取數(shù)據(jù)生成map
                map = (Map<String, Object>) XmlUtils.readMapXml(str);
            } catch (Exception e) {
                Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
            } finally {
                IoUtils.closeQuietly(str);
            }
        }
    } catch (ErrnoException e) {
       ...
    } catch (Throwable t) {
        thrown = t;
    }

    synchronized (mLock) {
    // mLoaded置為true
        mLoaded = true;
        mThrowable = thrown;

        // It's important that we always signal waiters, even if we'll make
        // them fail with an exception. The try-finally is pretty wide, but
        // better safe than sorry.
        try {
        // 如果正常加載完成极景,沒(méi)有異常
            if (thrown == null) {
            // 如果數(shù)據(jù)不為空
                if (map != null) {
                    mMap = map;//把加載的數(shù)據(jù)賦值給mMap
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                } else {
                // 數(shù)據(jù)為空則初始化一個(gè)mMap
                    mMap = new HashMap<>();
                }
            }
            // In case of a thrown exception, we retain the old map. That allows
            // any open editors to commit and store updates.
        } catch (Throwable t) {
            mThrowable = t;
        } finally {
        // 加載完成,喚醒等待mLock的線程
            mLock.notifyAll();
        }
    }
}
  1. 從mFile中加載數(shù)據(jù)碑幅,這個(gè)過(guò)程只加載一次戴陡,如果已經(jīng)加載過(guò)了,則不再加載
  2. 如果加載的過(guò)程中沟涨,發(fā)現(xiàn)備份文件還存在恤批,那么就說(shuō)明上次 新建/更新 數(shù)據(jù)時(shí)發(fā)生了異常,此時(shí)mFile文件的數(shù)據(jù)得不到保障裹赴,所以繼續(xù)采用備份文件的數(shù)據(jù)喜庞,放棄上次的操作
  3. 如果mFile里面沒(méi)有數(shù)據(jù),則重新初始化mMap棋返,如果有數(shù)據(jù)延都,則把讀取出來(lái)的數(shù)據(jù)賦值給mMap
  4. 數(shù)據(jù)讀取成功后,喚醒等待加載數(shù)據(jù)的線程

因?yàn)镾haredPreferences里面只有讀取數(shù)據(jù)的方法睛竣,下面看下getInt()

@Override
public int getInt(String key, int defValue) {
    synchronized (mLock) {
        awaitLoadedLocked();//判斷數(shù)據(jù)是否加載完成晰房,沒(méi)有加載完成,則阻塞等待
        Integer v = (Integer)mMap.get(key);//從mMap中去數(shù)據(jù)
        return v != null ? v : defValue;
    }
}

@GuardedBy("mLock")
private void awaitLoadedLocked() {
    if (!mLoaded) {
        // Raise an explicit StrictMode onReadFromDisk for this
        // thread, since the real read will be in a different
        // thread and otherwise ignored by StrictMode.
        BlockGuard.getThreadPolicy().onReadFromDisk();
    }
    while (!mLoaded) {
        try {
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
    if (mThrowable != null) {
        throw new IllegalStateException(mThrowable);
    }
}
  1. 首先判斷數(shù)據(jù)是否加載完成,如果沒(méi)有加載完成殊者,則阻塞當(dāng)前線程与境。加載完成后會(huì)被加載數(shù)據(jù)線程喚醒
  2. 數(shù)據(jù)加載完成后,從mMap中去數(shù)據(jù)
  3. 如果mMap中有數(shù)據(jù)則返回猖吴,沒(méi)有的話返回默認(rèn)值

其他的讀取數(shù)據(jù)的方法也是類似的,還有另外一個(gè)方法需要注意下

@Override
public Map<String, ?> getAll() {
    synchronized (mLock) {
        awaitLoadedLocked();
        //noinspection unchecked
        return new HashMap<String, Object>(mMap);
    }
}

調(diào)用這個(gè)方法會(huì)返回當(dāng)前shardePreferences的所有數(shù)據(jù)摔刁,但是一定不要更改里面的內(nèi)容,因?yàn)槿绻牧藘?nèi)容海蔽,那么SharedPreferences的數(shù)據(jù)一致性就得不到保證了共屈。

下面再分析下ShardePreferences的新增,更新和刪除的方法党窜,這些方法就需要EditorImpl 這個(gè)類來(lái)做

EditorImpl

private final Object mEditorLock = new Object();// 鎖
private final Map<String, Object> mModified = new HashMap<>(); // 更改的數(shù)據(jù)
private boolean mClear = false; //是否清空數(shù)據(jù)

下面看下putString()拗引,其他的putXXX()類似

@Override
public Editor putString(String key, @Nullable String value) {
// 加鎖,保證線程安全刑然,數(shù)據(jù)一致性
    synchronized (mEditorLock) {
        mModified.put(key, value);// 把值放入mModified
        return this;
    }
}
  1. 添加或者更新數(shù)據(jù)時(shí)寺擂,都會(huì)加鎖暇务,保證線程安全泼掠,數(shù)據(jù)一致性。然后會(huì)把數(shù)據(jù)放入mModified

添加完數(shù)據(jù)后還需要更新到文件和內(nèi)存中垦细,一般會(huì)調(diào)用commit()和apply()下面先看看apply()

@Override
public void apply() {
    final long startTime = System.currentTimeMillis();
    // 首先更新內(nèi)存中的數(shù)據(jù)择镇,
    final MemoryCommitResult mcr = commitToMemory();
    // 這是等待提交到磁盤的任務(wù)
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                // 如果寫入磁盤任務(wù)未完成,則一直等待writtenToDiskLatch是CountDownLatch
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }

                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };

// 把a(bǔ)waitCommit 添加QueuedWork的finisher的任務(wù)隊(duì)列
    QueuedWork.addFinisher(awaitCommit);

// 磁盤任務(wù)寫完之后需要執(zhí)行任務(wù)
    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
            // 執(zhí)行awaitCommit.run()
                awaitCommit.run();
                // 把a(bǔ)waitCommit 移除finisher隊(duì)列
                QueuedWork.removeFinisher(awaitCommit);
            }
        };

    // 調(diào)用寫入磁盤的任務(wù)
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

    // Okay to notify the listeners before it's hit disk
    // because the listeners should always get the same
    // SharedPreferences instance back, which has the
    // changes reflected in memory.
    notifyListeners(mcr);
}

上面的代碼中引入了MemoryCommitResult括改,MemoryCommitResult就是用來(lái)記錄更新的數(shù)據(jù)提交到內(nèi)存中的結(jié)果腻豌,比較簡(jiǎn)單,會(huì)放在下面再看

  1. 首先把數(shù)據(jù)更新到內(nèi)存中
  2. 構(gòu)造兩個(gè)任務(wù)嘱能,awaitCommit和postWriteRunnable
  3. 調(diào)用enqueueDiskWrite()吝梅,傳入postWriteRunnable

繼續(xù)追蹤enqueueDiskWrite

private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
     // commit()方法傳入的postWriteRunnable為null,而apply()傳入的不為null
    final boolean isFromSyncCommit = (postWriteRunnable == null);

    final Runnable writeToDiskRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mWritingToDiskLock) {
                // 寫入文件
                    writeToFile(mcr, isFromSyncCommit);
                }
                synchronized (mLock) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                // 運(yùn)行postWriteRunnable
                    postWriteRunnable.run();
                }
            }
        };

    // Typical #commit() path with fewer allocations, doing a write on
    // the current thread.
    // 如果isFromSyncCommit為true
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        // 如果提交的寫入磁盤數(shù)據(jù)的任務(wù)數(shù)為1惹骂,那么就直接執(zhí)行苏携,不再交給QueuedWork
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }
    // 默認(rèn)交給QueuedWork執(zhí)行
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
  1. 給isFromSyncCommit賦值,commit()方法中postWriteRunnable对粪,及isFromSyncCommit為true右冻,而apply()為false
  2. 構(gòu)建一個(gè)將數(shù)據(jù)寫入磁盤的任務(wù)writeToDiskRunnable
  3. isFromSyncCommit為true,并且寫入磁盤的任務(wù)數(shù)為1著拭,那么就會(huì)直接在當(dāng)前線程執(zhí)行纱扭,否則提交給QueuedWork,放在子線程中執(zhí)行

上面有個(gè)關(guān)鍵的方法writeToFile()

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
    ...

    boolean fileExists = mFile.exists();

    ...
    if (fileExists) {
        boolean needsWrite = false;

        // Only need to write if the disk state is older than this commit
        //只有當(dāng)前磁盤的state小于內(nèi)存的state儡遮,才會(huì)執(zhí)行寫入磁盤的任務(wù)
        if (mDiskStateGeneration < mcr.memoryStateGeneration) {
            if (isFromSyncCommit) {
                needsWrite = true;
            } else {
                synchronized (mLock) {
                    // 如果不是同步寫入的話乳蛾,那么只有當(dāng)前內(nèi)存state等于mcr.memoryStateGeneration才會(huì)執(zhí)行寫入
                    if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                        needsWrite = true;
                    }
                }
            }
        }
        
        // 如果不需要寫入,則返回
        if (!needsWrite) {
            mcr.setDiskWriteResult(false, true);
            return;
        }

        boolean backupFileExists = mBackupFile.exists();
        
          ....
          
          // 如果備份文件不存在
        if (!backupFileExists) {
        // 把mFile重命名為備份文件名,如果執(zhí)行失敗肃叶,則返回
            if (!mFile.renameTo(mBackupFile)) {
                Log.e(TAG, "Couldn't rename file " + mFile
                      + " to backup file " + mBackupFile);
                mcr.setDiskWriteResult(false, false);
                return;
            }
        } else {
        // 如果備份文件已經(jīng)存在了忆首,則把mFile文件刪除
            mFile.delete();
        }
    }

    // 嘗試寫入數(shù)據(jù)到mFile,如果寫入成功則把備份文件刪除被环;如果寫入的過(guò)程中發(fā)生了異常糙及,則把新文件刪除,下次會(huì)從備份文件讀取數(shù)據(jù)筛欢。這里就跟讀取數(shù)據(jù)的時(shí)候關(guān)聯(lián)上了
    try {
        FileOutputStream str = createFileOutputStream(mFile);
        
        // 如果數(shù)據(jù)流為null
        if (str == null) {
            mcr.setDiskWriteResult(false, false);
            return;
        }
        // 把mcr.mapToWriteToDisk數(shù)據(jù)寫入文件浸锨,此時(shí)所有的數(shù)據(jù)包括更新/新增的數(shù)據(jù)都在mcr.mapToWriteToDisk里面,因?yàn)樵赾ommitToMemory處理版姑,下面會(huì)分析commitToMemory
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);

        writeTime = System.currentTimeMillis();

        FileUtils.sync(str);

        fsyncTime = System.currentTimeMillis();

        str.close();
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);

        try {
            final StructStat stat = Os.stat(mFile.getPath());
            synchronized (mLock) {
                mStatTimestamp = stat.st_mtim;
                mStatSize = stat.st_size;
            }
        } catch (ErrnoException e) {
            // Do nothing
        }

        // 寫入數(shù)據(jù)成功柱搜,刪除備份文件
        mBackupFile.delete();

        // 更新磁盤的mDiskStateGeneration為mcr.memoryStateGeneration
        mDiskStateGeneration = mcr.memoryStateGeneration;
        ...

        mcr.setDiskWriteResult(true, true);

        mNumSync++;

        return;
    } catch (XmlPullParserException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    } catch (IOException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    }

    // 如果寫入數(shù)據(jù)的過(guò)程中發(fā)生了異常,那么就刪除mFile文件剥险,下次加載時(shí)會(huì)直接讀取備份文件
    if (mFile.exists()) {
        if (!mFile.delete()) {
            Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
        }
    }
    mcr.setDiskWriteResult(false, false);
}

上面是分析了寫入磁盤緩存的方法聪蘸,下面再看下寫入內(nèi)存緩存的方法commitToMemory()

private MemoryCommitResult commitToMemory() {
    long memoryStateGeneration;
    List<String> keysModified = null;
    Set<OnSharedPreferenceChangeListener> listeners = null;
    Map<String, Object> mapToWriteToDisk;

    synchronized (SharedPreferencesImpl.this.mLock) {
        // 只會(huì)在調(diào)用了commit和apply方法時(shí),才會(huì)執(zhí)行內(nèi)存緩存表制,把數(shù)據(jù)更新到內(nèi)存
        if (mDiskWritesInFlight > 0) {
            // 如果當(dāng)前正在向磁盤寫入mMap健爬,那么禁止修改mMap
            // 對(duì)當(dāng)前的mMap進(jìn)行clone,下面就只會(huì)操作這個(gè)mMap,因?yàn)閔ashmap不是線程安全么介,SharedPreference最重要的必須保證的就是數(shù)據(jù)一致性
            mMap = new HashMap<String, Object>(mMap);
        }
        mapToWriteToDisk = mMap;//把mMap賦值給mapToWriteToDisk
        mDiskWritesInFlight++;// 寫入磁盤的任務(wù)數(shù) 加1

        boolean hasListeners = mListeners.size() > 0;
        if (hasListeners) {
            keysModified = new ArrayList<String>();
            listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
        }

        synchronized (mEditorLock) {
            boolean changesMade = false;
            // 如果調(diào)用了clear方法娜遵,clear標(biāo)志就會(huì)為true
            if (mClear) {
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    // 清楚數(shù)據(jù),此時(shí)清楚的只是mMap里面的數(shù)據(jù)壤短,并沒(méi)有清除mModified设拟,即更新后的數(shù)據(jù)沒(méi)有清除
                    mapToWriteToDisk.clear();
                }
                mClear = false;
            }
    
                // 遍歷修改的數(shù)據(jù)
            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                // 如果v == null 或者v== this,這里this就是EditorImpl對(duì)象,就代表需要移除當(dāng)前k
                if (v == this || v == null) {
                    if (!mapToWriteToDisk.containsKey(k)) {
                        continue;
                    }
                    mapToWriteToDisk.remove(k);
                } else {
                    if (mapToWriteToDisk.containsKey(k)) {
                        Object existingValue = mapToWriteToDisk.get(k);
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    // 把k久脯,v寫入mapToWriteToDisk
                    mapToWriteToDisk.put(k, v);
                }

                changesMade = true;
                if (hasListeners) {
                    keysModified.add(k);
                }
            }

             // 修改后的數(shù)據(jù)集清空纳胧,此時(shí)所有的數(shù)據(jù),原來(lái)的數(shù)據(jù)和更新后的數(shù)據(jù)全部在mapToWriteToDisk里面
            mModified.clear();
                
                // 如果有更新
            if (changesMade) {
                mCurrentMemoryStateGeneration++;
            }
                // 更新memoryStateGeneration為mCurrentMemoryStateGeneration
            memoryStateGeneration = mCurrentMemoryStateGeneration;
        }
    }
    return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
            mapToWriteToDisk);
}

只有在調(diào)用了commit或者apply方法時(shí)帘撰,才會(huì)調(diào)用這個(gè)方法跑慕。這個(gè)方法比較簡(jiǎn)單,就是把mModified中的數(shù)據(jù)和原來(lái)的數(shù)據(jù)mMap做添加骡和,更新相赁,刪除的操作。得到最新的數(shù)據(jù)mapToWriteToDisk慰于,更新內(nèi)存狀態(tài)钮科,最終構(gòu)造成MemoryCommitResult返回

上面是apply方法,下面再看commit()婆赠,跟apply基本一樣

@Override
public boolean commit() {
    long startTime = 0;

    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }

    MemoryCommitResult mcr = commitToMemory();//更新內(nèi)存的映射

    // 執(zhí)行硬盤緩存的任務(wù)绵脯,傳入null佳励,表示可以在當(dāng)前線程執(zhí)行寫入磁盤的操作
    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
    // 這里是關(guān)鍵,這里會(huì)阻塞當(dāng)前線程 直到磁盤緩存任務(wù)執(zhí)行完成
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    } finally {
        if (DEBUG) {
            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                    + " committed after " + (System.currentTimeMillis() - startTime)
                    + " ms");
        }
    }
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

分析完apply方法后commit()就很容易了蛆挫,commit跟apply的區(qū)別就在與commit允許在當(dāng)前線程執(zhí)行寫入磁盤的任務(wù)赃承,并且不管寫入磁盤是否在當(dāng)前任務(wù)執(zhí)行,commit都會(huì)阻塞當(dāng)前線程悴侵,等待磁盤更新完成瞧剖。

MemoryCommitResult

這個(gè)類比較簡(jiǎn)單,先看相關(guān)的屬性

final long memoryStateGeneration;// 當(dāng)前的內(nèi)存state
@Nullable final List<String> keysModified;//做了改變的值的列表
@Nullable final Set<OnSharedPreferenceChangeListener> listeners;
final Map<String, Object> mapToWriteToDisk;// 存放此次內(nèi)存更新后的數(shù)據(jù)
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);//用來(lái)等待磁盤寫入完成

只有一個(gè)關(guān)鍵方法

void setDiskWriteResult(boolean wasWritten, boolean result) {
    this.wasWritten = wasWritten;
    writeToDiskResult = result;
    writtenToDiskLatch.countDown();// writtenToDiskLatch計(jì)數(shù)減一,喚醒等待此任務(wù)的線程
}

上面的方法是在設(shè)置最終磁盤更新任務(wù)的結(jié)果可免,并且將writtenToDiskLatch計(jì)數(shù)減一抓于,這樣等待磁盤更新任務(wù)的線程會(huì)被喚醒,繼續(xù)執(zhí)行浇借。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捉撮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子妇垢,更是在濱河造成了極大的恐慌巾遭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闯估,死亡現(xiàn)場(chǎng)離奇詭異灼舍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)睬愤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門片仿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人尤辱,你說(shuō)我怎么就攤上這事∠崞瘢” “怎么了光督?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)塔粒。 經(jīng)常有香客問(wèn)我结借,道長(zhǎng),這世上最難降的妖魔是什么卒茬? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任船老,我火速辦了婚禮,結(jié)果婚禮上圃酵,老公的妹妹穿的比我還像新娘柳畔。我一直安慰自己,他們只是感情好郭赐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布薪韩。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俘陷。 梳的紋絲不亂的頭發(fā)上罗捎,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音拉盾,去河邊找鬼桨菜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛捉偏,可吹牛的內(nèi)容都是我干的雷激。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼告私,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼屎暇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤部宿,失蹤者是張志新(化名)和其女友劉穎赚爵,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挤巡,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年酷麦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了矿卑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沃饶,死狀恐怖母廷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糊肤,我是刑警寧澤琴昆,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站馆揉,受9級(jí)特大地震影響业舍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜升酣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一舷暮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧噩茄,春花似錦下面、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)券膀。三九已至,卻和暖如春驯遇,著一層夾襖步出監(jiān)牢的瞬間芹彬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工叉庐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舒帮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓陡叠,卻偏偏與公主長(zhǎng)得像玩郊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子枉阵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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