Android 數(shù)據(jù)庫(kù) ObjectBox 源碼解析

如若不關(guān)心實(shí)現(xiàn)細(xì)節(jié)可直接查看“ObjectBox 架構(gòu)”怀樟、“總結(jié)”這兩部分內(nèi)容。(簡(jiǎn)書(shū)不支持錨點(diǎn)(盆佣;′⌒`))

一往堡、ObjectBox 是什么?

greenrobot 團(tuán)隊(duì)(現(xiàn)有 EventBus共耍、greenDAO 等開(kāi)源產(chǎn)品)推出的又一數(shù)據(jù)庫(kù)開(kāi)源產(chǎn)品虑灰,主打移動(dòng)設(shè)備、支持跨平臺(tái)痹兜,最大的優(yōu)點(diǎn)是速度快穆咐、操作簡(jiǎn)潔,目前已在實(shí)際項(xiàng)目中踩坑字旭。下面將逐步分析這一堪稱(chēng)超快數(shù)據(jù)庫(kù)的 SDK 源碼(Android 部分)对湃,一起探個(gè)究竟。

ObjectBox Android 介紹

市面上已經(jīng)有諸如 greenDAO谐算、Realm熟尉、Room 等眾多開(kāi)源產(chǎn)品,至于為什么還選擇 ObjectBox洲脂,暫不在本文討論范圍內(nèi)斤儿。

二剧包、ObjectBox 怎么用?

在開(kāi)始源碼解析之前往果,先介紹一下用法疆液。
1、項(xiàng)目配置依賴(lài)陕贮,根據(jù)官網(wǎng)介紹一步步操作即可堕油,比較簡(jiǎn)單。
2肮之、創(chuàng)建業(yè)務(wù)實(shí)體類(lèi)掉缺,添加@Entity,同時(shí)通過(guò)@Id指定主鍵戈擒,之后Build -> Make Project眶明。

創(chuàng)建業(yè)務(wù)實(shí)體,添加 ObjectBox 注解

3筐高、ObjectBox Gradle 插件會(huì)在項(xiàng)目的 build 目錄下生成 MyObjectBox 類(lèi)搜囱,以及輔助類(lèi)(如圖中的User_UserCursor柑土、Order_蜀肘、OrderCursor),接下來(lái)直接調(diào)用MyObjectBox 稽屏。

插件自動(dòng)生成數(shù)據(jù)庫(kù)輔助類(lèi)

4扮宠、通過(guò) MyObjectBox 類(lèi)獲取數(shù)據(jù)庫(kù)(BoxStore),通過(guò)數(shù)據(jù)庫(kù)獲取對(duì)應(yīng)的表(Box)狐榔,進(jìn)行 CRUD 操作涵卵。
創(chuàng)建數(shù)據(jù)庫(kù),獲取表荒叼,增刪改查

總結(jié):實(shí)際開(kāi)發(fā)過(guò)程中的感受,使用簡(jiǎn)單典鸡,配合 ObjectBrowser 直接在瀏覽器查看數(shù)據(jù)被廓,開(kāi)發(fā)體驗(yàn)好。

但是萝玷,為什么插件要自動(dòng)創(chuàng)建MyObjectBoxUser_嫁乘、UserCursorOrder_球碉、OrderCursor類(lèi)呢蜓斧?他們又分別起什么作用?SDK 內(nèi)部如何運(yùn)行睁冬?

三挎春、ObjectBox 架構(gòu)

要回答以上問(wèn)題看疙,先介紹一下 ObjectBox 架構(gòu)。

ObjectBox 架構(gòu)

從下往上看直奋,主要分成 Engine能庆、Core、Extentions 三層脚线。

  1. Engine 層屬于 Native搁胆,是整個(gè)數(shù)據(jù)庫(kù)的引擎,可跨平臺(tái)邮绿。
    目前已支持 Android(4.0+)渠旁、Linux(64位)、Windows(64位)船逮,而 macOS顾腊、iOS 的支持在開(kāi)發(fā)中。
    大部分 Java 層的數(shù)據(jù)庫(kù)操作都調(diào)用了 Native 方法傻唾,但 Native 部分目前沒(méi)有開(kāi)源投慈。

  2. CoreExtentions 屬于 Java。
    Core 層是核心冠骄,負(fù)責(zé)數(shù)據(jù)庫(kù)管理伪煤、CRUD 以及和 Native 通信;
    Extentions 提供了諸如 Reactive凛辣、LiveData抱既、Kotlin 等一系列的擴(kuò)展。

下面將重點(diǎn)對(duì) Core 層進(jìn)行解析扁誓。

四防泵、ObjectBox 源碼解析

4.1 Entity

指的是添加了@Entity 注解的業(yè)務(wù)實(shí)體,如上文中提到的 User 類(lèi)蝗敢,一個(gè) Entity 可看做一張數(shù)據(jù)庫(kù)表捷泞。從上文可知 Gradle 插件自動(dòng)生成了對(duì)應(yīng)的 User_UserCursor 類(lèi)寿谴,其中 User_ 就是 EntityInfo锁右。

User

4.2 EntityInfo

和 Entity 是成對(duì)出現(xiàn)的,目的是保存 Entity 的相關(guān)信息讶泰,如名稱(chēng)咏瑟、屬性(字段)等,用于后續(xù)的查詢(xún)等一系列操作痪署。


User_(User 類(lèi)的 EntityInfo)

4.3 MyObjectBox

除了User_码泞,插件還自動(dòng)生成MyObjectBox 類(lèi),它只對(duì)外提供了 builder 方法返回 BoxStoreBuilder狼犯,用來(lái)構(gòu)造數(shù)據(jù)庫(kù)余寥。

    /**
     * 創(chuàng)建 BoxStore 構(gòu)造器
     *
     * @return 構(gòu)造器
     */
    public static BoxStoreBuilder builder() {
        BoxStoreBuilder builder = new BoxStoreBuilder(getModel());
        builder.entity(User_.__INSTANCE);
        builder.entity(Order_.__INSTANCE);
        return builder;
    }

主要是做了兩件事情领铐,一個(gè)是getModel返回 Model,注意這里的 Model 是給 Native 層創(chuàng)建數(shù)據(jù)庫(kù)用的劈狐,數(shù)據(jù)格式是 byte[]罐孝;

創(chuàng)建 Model

另一個(gè)是通過(guò)entity把所有 EntityInfo 保存起來(lái),后續(xù) Java 層的一系列操作都會(huì)用到肥缔。

可見(jiàn)插件把 @Entity 生成為 EntityInfo 和 Model莲兢,前者是給 Java 層用,后者是給 Native 層用续膳。開(kāi)發(fā)者會(huì)經(jīng)常和 EntityInfo 打交道改艇,但卻不會(huì)感知到 Model 的存在。

4.4 BoxStore

BoxStore 代表著整個(gè)數(shù)據(jù)庫(kù)坟岔,由 BoxStoreBuilder#build 生成(通過(guò) BoxStoreBuilder 可以進(jìn)行一些定制化配置谒兄,如最大讀并發(fā)數(shù)、最大容量社付、數(shù)據(jù)庫(kù)文件名等)承疲,從源碼中可以看出 BoxStoreBuilder#build 方法 new 了一個(gè) BoxStore 對(duì)象并返回:

    public BoxStore build() {
        if (directory == null) {
            name = dbName(name);
            directory = getDbDir(baseDirectory, name);
        }
        return new BoxStore(this);
    }

BoxStore 的作用:

  1. 加載所有 Native 庫(kù)
  2. 調(diào)用 Native 方法創(chuàng)建數(shù)據(jù)庫(kù)
  3. 調(diào)用 Native 方法依次創(chuàng)建 Entity
  4. 創(chuàng)建并管理 Box(和 Entity對(duì)應(yīng),下文介紹)
  5. 創(chuàng)建并管理 Transaction(所有數(shù)據(jù)庫(kù)操作都會(huì)放到事務(wù)中鸥咖,下文介紹)
  6. 提供數(shù)據(jù)訂閱(有興趣可自行分析 Reactive 拓展模塊)

其中燕鸽,1、2啼辣、3 都在 BoxStore 構(gòu)造方法中完成啊研,來(lái)看看代碼:

    BoxStore(BoxStoreBuilder builder) {
        // 1、加載 Native
        NativeLibraryLoader.ensureLoaded();
         …… // 省略各種校驗(yàn)
        // 2鸥拧、調(diào)用 Native 方法創(chuàng)建數(shù)據(jù)庫(kù)党远,并返回句柄(其實(shí)就是id)
        // 后續(xù)一系列操作 Native 方法的調(diào)用都要回傳這個(gè)句柄
        handle = nativeCreate(canonicalPath, builder.maxSizeInKByte, builder.maxReaders, builder.model);
        ……
        for (EntityInfo entityInfo : builder.entityInfoList) {
                ……
                // 3、調(diào)用 Native 方法依次注冊(cè) Entity富弦,并返回句柄
                int entityId = nativeRegisterEntityClass(handle, entityInfo.getDbName(), entityInfo.getEntityClass());
                entityTypeIdByClass.put(entityInfo.getEntityClass(), entityId);
        }
        ……
    }

構(gòu)造函數(shù)執(zhí)行完沟娱,數(shù)據(jù)庫(kù)就已準(zhǔn)備就緒。

4.5 Box

通過(guò)調(diào)用 public <T> Box<T> boxFor(Class<T> entityClass) 方法腕柜,BoxStore 會(huì)為對(duì)應(yīng)的 EntityClass 生成并管理 Box(和 EntityClass 一一對(duì)應(yīng)):

    /**
     * Returns a Box for the given type. Objects are put into (and get from) their individual Box.
     */
    public <T> Box<T> boxFor(Class<T> entityClass) {
        Box box = boxes.get(entityClass);
        if (box == null) {
            …… // 省略
            synchronized (boxes) {
                box = boxes.get(entityClass);
                if (box == null) {
                    // 創(chuàng)建 Box花沉,傳入 BoxStore 實(shí)例,以及 EntityClass
                    box = new Box<>(this, entityClass);
                    boxes.put(entityClass, box);
                }
            }
        }
        return box;
    }

Box 的職責(zé)就是進(jìn)行 Entity 的 CRUD 操作媳握,在深入分析其 CRUD 操作之前,必須先了解兩個(gè)概念:Transaction(事務(wù))Cursor(游標(biāo))磷脯。

4.6 Transaction

Transaction(事務(wù))是數(shù)據(jù)庫(kù)管理系統(tǒng)執(zhí)行過(guò)程中的一個(gè)邏輯單位蛾找,在 BoxStore 的介紹一節(jié)中提到其主要作用之一是“創(chuàng)建并管理 Transaction”。其實(shí)赵誓,在 ObjectBox 中打毛,所有 Transaction 對(duì)象都是通過(guò) BoxStore 的兩個(gè)內(nèi)部方法 beginTx()beginReadTx() 生成柿赊,后者生成一個(gè)只讀 Transaction(不允許寫(xiě)入,可復(fù)用幻枉,性能會(huì)更好)碰声。

    @Internal
    public Transaction beginTx() {
        // 1、調(diào)用 Native 方法生成事務(wù)熬甫,并返回其句柄
        long nativeTx = nativeBeginTx(handle);
        // 2胰挑、生成 Transaction 對(duì)象,傳入 BoxStore椿肩、Native 事務(wù)句柄瞻颂、已提交事務(wù)數(shù)量(當(dāng)該事務(wù)準(zhǔn)備提交時(shí),用來(lái)判斷有沒(méi)有被其他事務(wù)搶先提交郑象,有點(diǎn)繞哈贡这,可以不管)
        Transaction tx = new Transaction(this, nativeTx, initialCommitCount);
        synchronized (transactions) {
            transactions.add(tx);
        }
        return tx;
    }
    @Internal
    public Transaction beginReadTx() {
        ……
        // 唯一不同的是,這里調(diào)用了 nativeBeginReadTx 生成只讀事務(wù)
        long nativeTx = nativeBeginReadTx(handle);
        ……
    }

從以上兩個(gè)方法中厂榛,可以發(fā)現(xiàn)所有的事務(wù)最終都是調(diào)用 Native 生成盖矫,Transaction 對(duì)象只是持有其句柄(一個(gè)類(lèi)型為 long 的變量),以便后續(xù)各個(gè)操作時(shí)回傳給 Native击奶,如:

    /** 調(diào)用 Transaction 對(duì)象的提交方法 */
    public void commit() {
        checkOpen();
        // 交由 Native 進(jìn)行事務(wù)提交
        int[] entityTypeIdsAffected = nativeCommit(transaction);
        store.txCommitted(this, entityTypeIdsAffected);
    }
    /** 調(diào)用 Transaction 對(duì)象的中斷方法 */
    public void abort() {
        checkOpen();
        // 交由 Native 進(jìn)行事務(wù)中斷
        nativeAbort(transaction);
    }

此外辈双,在 ObjectBox 中,事務(wù)分為兩類(lèi)“顯式事務(wù)”和“隱式事務(wù)”正歼。

“顯式事務(wù)”是指開(kāi)發(fā)者直接調(diào)用以下方法運(yùn)行的事務(wù):
BoxStore#runInTx(Runnable)
BoxStore#runInReadTx(Runnable)
BoxStore#runInTxAsync(Runnable,TxCallback)
BoxStore#callInTx(Callable)
BoxStore#callInReadTx(Callable)
BoxStore#callInTxAsync(Callable,TxCallback)

“隱式事務(wù)”是指對(duì)開(kāi)發(fā)者透明的辐马,框架隱式創(chuàng)建和管理的事務(wù),如下面會(huì)分析到的Box#get(long)方法局义。

有了事務(wù)喜爷,就可以在其中進(jìn)行一系列數(shù)據(jù)庫(kù)的操作,那么怎么創(chuàng)建“操作”萄唇?這些“操作”又是如何執(zhí)行檩帐?。

4.7 Cursor

上文中所說(shuō)的“操作”另萤,實(shí)際上是 Cursor (游標(biāo))湃密。

我們?cè)賮?lái)回顧一下,文章一開(kāi)始我們提到 Gradle 插件會(huì)為 User 這個(gè) Entity 生成一個(gè)叫做UserCursor的文件四敞,這就是所有針對(duì)User 的 CRUD 操作真正發(fā)生的地方——游標(biāo)泛源,來(lái)看看其內(nèi)容。

UserCursor 文件

UserCursor 繼承了 Cursor<T> 忿危,提供 Factory 供創(chuàng)建時(shí)調(diào)用达箍,同時(shí)實(shí)現(xiàn)了 getId 方法,以及put 方法實(shí)現(xiàn)寫(xiě)入數(shù)據(jù)庫(kù)操作铺厨。

上文中提到 Box 的職責(zé)是 CRUD缎玫,其實(shí)最終都落實(shí)到了游標(biāo)身上硬纤。雖然開(kāi)發(fā)過(guò)程中不會(huì)直接調(diào)用 Cursor 類(lèi),但是有必要弄明白其中原理赃磨。

首先筝家,所有游標(biāo)的創(chuàng)建,必須調(diào)用 Transation 的 createCursor 方法(注意看注釋?zhuān)?/p>

    public <T> Cursor<T> createCursor(Class<T> entityClass) {
        checkOpen();
        EntityInfo entityInfo = store.getEntityInfo(entityClass);
        CursorFactory<T> factory = entityInfo.getCursorFactory();

        // 1邻辉、調(diào)用 Native 創(chuàng)建游標(biāo)溪王,傳入 transaction (事務(wù)句柄),dbName恩沛,entityClass 三個(gè)參數(shù)在扰,并返回句柄(游標(biāo)ID)
        // 通過(guò)這三個(gè)參數(shù),把[游標(biāo)]和[事務(wù)]雷客、[數(shù)據(jù)庫(kù)表名]芒珠、[EntityClass]進(jìn)行綁定
        long cursorHandle = nativeCreateCursor(transaction, entityInfo.getDbName(), entityClass);

        // 2、調(diào)用 factory 創(chuàng)建 Cursor 對(duì)象搅裙,傳入游標(biāo)句柄(后續(xù)一系列操作會(huì)回傳給 Native)
        return factory.createCursor(this, cursorHandle, store);
    }

其次皱卓,拿到游標(biāo),就可以調(diào)用相關(guān)方法部逮,進(jìn)行 CRUD 操作:

// Cursor<T> 抽象類(lèi)

    public T get(long key) {
        // Native 查詢(xún)娜汁,傳入游標(biāo)句柄、ID值
        return (T) nativeGetEntity(cursor, key);
    }

    public T next() {
        // Native 查詢(xún)下一條兄朋,傳入游標(biāo)句柄
        return (T) nativeNextEntity(cursor);
    }

    public T first() {
        // Native 查詢(xún)第一條掐禁,傳入游標(biāo)句柄
        return (T) nativeFirstEntity(cursor);
    }

    public void deleteEntity(long key) {
        // Native 刪除,傳入游標(biāo)句柄颅和、ID值
        nativeDeleteEntity(cursor, key);
    }
// UserCursor 類(lèi) (extends Cursor<User>)

    @Override
    public final long put(User entity) {
        ……
        // Native 進(jìn)行插入/更新傅事,傳入游標(biāo)句柄
        long __assignedId = collect313311(cursor, entity.getId(),……);
        ……
        return __assignedId;
    }

Cursor 類(lèi)提供了一系列 collectXXXXXX 的方法供數(shù)據(jù)插入/更新,比較有意思的思路峡扩,感興趣的可以自行閱讀蹭越。

而游標(biāo)的 CRUD 操作(如寫(xiě)),最終都是要依靠事務(wù)才能完成提交教届。

那么响鹃,又回到 Box 一節(jié)的問(wèn)題,Box 是如何把TransactionCursor結(jié)合起來(lái)完成 CRUD 操作的呢案训?

4.8 Box 的 CRUD 操作

下圖是開(kāi)發(fā)者直接調(diào)用 Box 進(jìn)行 CRUD 操作的所有接口买置。

Box CRUD 接口

我們挑兩個(gè)例子來(lái)分析。

4.8.1 查詢(xún) Box#get(long)

public T get(long id) {
    // 1强霎、獲取一個(gè)只讀游標(biāo)
    Cursor<T> reader = getReader();
    try {
        // 2堕义、調(diào)用游標(biāo)的 get 方法
        return reader.get(id);
    } finally {
        // 3、釋放,只讀事務(wù)只會(huì)回收倦卖,以便復(fù)用
        releaseReader(reader);
    }
}

從“游標(biāo)”一節(jié)中我們知道,游標(biāo)必須由事務(wù)創(chuàng)建椿争,我們來(lái)看看Box#getReader()方法:

Cursor<T> getReader() {
    // 1怕膛、判斷當(dāng)前線(xiàn)程是否有可用事務(wù)和可用游標(biāo)(ThreadLocal<Cursor<T>>變量保存)
    Cursor<T> cursor = getActiveTxCursor();
    if (cursor != null) {
        return cursor;
    } else {
        …… (省略緩存處理邏輯)
        // 2、當(dāng)前線(xiàn)程無(wú)可用游標(biāo)秦踪,調(diào)用 BoxStore 啟動(dòng)只讀事務(wù)褐捻、創(chuàng)建游標(biāo)
        cursor = store.beginReadTx().createCursor(entityClass);
        // 3、緩存游標(biāo)椅邓,下次使用
        threadLocalReader.set(cursor);
    }
    return cursor;
}

所以 Box 所有查詢(xún)操作柠逞,先去 BoxStore 獲取一個(gè)只讀游標(biāo),隨后調(diào)用其 Cursor#get(long) 方法并返回結(jié)果景馁,最后再回收該游標(biāo)及其對(duì)應(yīng)的事務(wù)板壮。

4.8.2 添加 Box#put(T)

public long put(T entity) {
    // 1、獲取游標(biāo)(默認(rèn)可以讀寫(xiě))
    Cursor<T> cursor = getWriter();
    try {
        // 2合住、調(diào)用游標(biāo)的 put 方法
        long key = cursor.put(entity);
        // 3绰精、事務(wù)提交
        commitWriter(cursor);
        return key;
    } finally {
        // 4、釋放透葛,讀寫(xiě)事務(wù)會(huì)被銷(xiāo)毀笨使,無(wú)法復(fù)用
        releaseWriter(cursor);
    }
}

getReader 方法不同,因?yàn)椤皩?xiě)事務(wù)”無(wú)法復(fù)用僚害,所以getWriter 少了緩存事務(wù)的邏輯硫椰,完整代碼:

Cursor<T> getWriter() {
    // 1、和 getReader 一樣萨蚕,判斷當(dāng)前線(xiàn)程是否有可用事務(wù)和可用游標(biāo)
    Cursor<T> cursor = getActiveTxCursor();
    if (cursor != null) {
        return cursor;
    } else {
        // 2靶草、當(dāng)前線(xiàn)程無(wú)可用游標(biāo),調(diào)用 BoxStore 啟動(dòng)事務(wù)门岔、創(chuàng)建游標(biāo)
        Transaction tx = store.beginTx();
        try {
            return tx.createCursor(entityClass);
        } catch (RuntimeException e) {
            tx.close();
            throw e;
        }
    }
}

所以 Box 所有添加操作爱致,先去 BoxStore 獲取一個(gè)游標(biāo),隨后調(diào)用其 Cursor#put(T) 方法并返回 id寒随,最后再銷(xiāo)毀該游標(biāo)及其對(duì)應(yīng)的事務(wù)糠悯。

當(dāng)我們調(diào)用 Box 相關(guān) CRUD 操作時(shí),事務(wù)妻往、游標(biāo)的處理都在 Box 及 BoxStore 內(nèi)部處理完成互艾,對(duì)開(kāi)發(fā)者是透明的,也就是上面說(shuō)到的“隱式事務(wù)”讯泣。

另外纫普,Box 只能夠滿(mǎn)足根據(jù)“主鍵”的查詢(xún),如果查詢(xún)條件涉及到“過(guò)濾”、“多屬性聯(lián)合”昨稼、“聚合”等比較復(fù)雜的节视,得借助 Query 類(lèi)。

4.9 Query

我們先來(lái)看看 Query 用法:

Query 用法

首先通過(guò) Box#query() 調(diào)用 Native 方法獲取 QueryBuilder 對(duì)象(持有 Native 句柄)假栓。針對(duì) QueryBuilder 可以設(shè)置各種查詢(xún)條件寻行,比如 equal(Property,long)

public QueryBuilder<T> equal(Property property, long value) {
    ……
    // 調(diào)用 Native 方法,設(shè)置 equal 查詢(xún)條件匾荆,傳入屬性 id 及目標(biāo)數(shù)值
    checkCombineCondition(nativeEqual(handle, property.getId(), value));
    return this;
}

再通過(guò) QueryBuilder#build() 調(diào)用 Native 方法生成 Query 對(duì)象(持有 Native 句柄)拌蜘,最后,通過(guò) Query#find() 返回所需數(shù)據(jù)牙丽,且 Query 對(duì)象可以重復(fù)使用简卧。

在理解了事務(wù)、游標(biāo)等概念后烤芦,很容易理解 QueryBuilder 以及 Query举娩,更多代碼就不貼出來(lái)了。

五拍棕、總結(jié)

以上晓铆,我們逐一分析了 ObjectBox 架構(gòu) Core 層各核心類(lèi)的作用及其關(guān)系,總結(jié)起來(lái)就是:

Core 層關(guān)系圖

參考資料

ObjectBox 官網(wǎng)

ObjectBox 文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绰播,一起剝皮案震驚了整個(gè)濱河市骄噪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蠢箩,老刑警劉巖链蕊,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異谬泌,居然都是意外死亡滔韵,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)掌实,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)陪蜻,“玉大人,你說(shuō)我怎么就攤上這事贱鼻⊙缏簦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵邻悬,是天一觀(guān)的道長(zhǎng)症昏。 經(jīng)常有香客問(wèn)我,道長(zhǎng)父丰,這世上最難降的妖魔是什么肝谭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上攘烛,老公的妹妹穿的比我還像新娘魏滚。我一直安慰自己,他們只是感情好坟漱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布栏赴。 她就那樣靜靜地躺著,像睡著了一般靖秩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竖瘾,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天沟突,我揣著相機(jī)與錄音,去河邊找鬼捕传。 笑死惠拭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的庸论。 我是一名探鬼主播职辅,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼聂示!你這毒婦竟也來(lái)了域携?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鱼喉,失蹤者是張志新(化名)和其女友劉穎秀鞭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扛禽,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锋边,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了编曼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豆巨。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖掐场,靈堂內(nèi)的尸體忽然破棺而出往扔,到底是詐尸還是另有隱情,我是刑警寧澤刻肄,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布瓤球,位于F島的核電站,受9級(jí)特大地震影響敏弃,放射性物質(zhì)發(fā)生泄漏卦羡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绿饵。 院中可真熱鬧欠肾,春花似錦、人聲如沸拟赊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吸祟。三九已至瑟慈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屋匕,已是汗流浹背葛碧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留过吻,地道東北人进泼。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纤虽,于是被迫代替她去往敵國(guó)和親乳绕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345