Android Jetpack架構(gòu)組件之Room入門及源碼分析

——你拼命掙錢的樣子尼斧,雖然有些狼狽;但是你自己靠自己的樣子,真的很美棚潦!

前言

——這篇主要是梳理一下Jetpack架構(gòu)組件之一的Room,并結(jié)合樓主所學(xué)做個(gè)總結(jié)膝昆。面向那些還沒(méi)接觸Room的同學(xué)們丸边。看完這篇可以快速了解它荚孵,并輕松使用妹窖。也想請(qǐng)教前輩們指點(diǎn)文章中的錯(cuò)誤或不足的地方。本篇只描述Room处窥,不會(huì)拓展額外的知識(shí),若想了解更多關(guān)于Jetpack組件知識(shí)可以看樓主寫(xiě)的Jetpack專欄嘱吗。

一、簡(jiǎn)介

(1)是什么

——Room 是google推出的Jetpack架構(gòu)組件之一滔驾,在SQLite上提供了一個(gè)抽象層谒麦,允許流暢地訪問(wèn)數(shù)據(jù)庫(kù),同時(shí)利用SQLite的全部功能哆致。

Room包含3個(gè)重要組件:

  • Database:包含數(shù)據(jù)庫(kù)容器绕德,并作為到應(yīng)用程序的持久關(guān)系數(shù)據(jù)的基礎(chǔ)連接的主要訪問(wèn)點(diǎn)
  • Entity:表示數(shù)據(jù)庫(kù)中的一個(gè)表。
  • DAO:包含用于訪問(wèn)數(shù)據(jù)庫(kù)的方法

Room 不同組件之間的關(guān)系:

(2)有什么用

——這個(gè)庫(kù)可以幫助你在運(yùn)行應(yīng)用的設(shè)備上創(chuàng)建應(yīng)用數(shù)據(jù)的緩存摊阀。這個(gè)緩存是應(yīng)用的唯一真實(shí)來(lái)源耻蛇,允許用戶查看應(yīng)用內(nèi)的關(guān)鍵信息的一致副本,不管用戶是否有互聯(lián)網(wǎng)連接

可以簡(jiǎn)單的理解為Room是對(duì)SQLite的一個(gè)封裝胞此,使開(kāi)發(fā)者們更容易使用SQLite臣咖。

(3)有什么優(yōu)點(diǎn)

  • 通過(guò)簡(jiǎn)單的注釋,room注解處理器會(huì)幫開(kāi)發(fā)者生成創(chuàng)建數(shù)據(jù)庫(kù)所需的代碼漱牵。
  • 使用簡(jiǎn)潔夺蛇,代碼量少
  • 結(jié)構(gòu)清晰,易于維護(hù)

?二酣胀、基本使用

(1)添加依賴

    implementation "android.arch.persistence.room:runtime:1.1.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.1.0"

(2)建立一個(gè)表

/**
 * 通過(guò)@Entity 注解 建立一個(gè)表
 */
@Entity
public class Student {
    @PrimaryKey(autoGenerate = true) int id;
    @ColumnInfo String name;
    @ColumnInfo String sex;
    @ColumnInfo int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

(3)創(chuàng)建數(shù)據(jù)庫(kù)

/**
 * 創(chuàng)建數(shù)據(jù)庫(kù)
 * 通過(guò)entities 指定數(shù)據(jù)庫(kù)中的表
 * version指定當(dāng)前數(shù)據(jù)庫(kù)版本號(hào)
 */
@Database(entities = {Student.class},version = 1)
public abstract class RoomDbManager extends RoomDatabase {
    public abstract StudentDao getStudentDao();
}

(4)創(chuàng)建訪問(wèn)數(shù)據(jù)庫(kù)的方法

/**
 * 創(chuàng)建訪問(wèn)數(shù)據(jù)庫(kù)的方法
 */
@Dao
public interface StudentDao {

    @Insert
    void insertOne(Student student);

    @Delete
    void deleteOne(Student student);

    @Update
    void update(Student student);

    @Query("SELECT * FROM Student")
    List<Student> getAll();
}

(5)使用步驟

 private RoomDbManager roomDb;
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //步驟一:獲取數(shù)據(jù)庫(kù)實(shí)例
        if (room_blcs == null) {
            roomDb= Room.databaseBuilder(getApplicationContext(),
                    RoomDbManager.class, "room_blcs").build();
        }
        //步驟二:獲取訪問(wèn)數(shù)據(jù)庫(kù)的方法實(shí)例
        StudentDao studentDao = roomDb.getStudentDao();

        //步驟三:訪問(wèn)StudentDao 方法執(zhí)行數(shù)據(jù)庫(kù)操作:增刪改查
        //注:這些方法不能在主線程(UI線程)上執(zhí)行刁赦,需要?jiǎng)?chuàng)建新的線程來(lái)執(zhí)行這些耗時(shí)操作。

        //增:studentDao.insertOne(student);

        //刪:studentDao.deleteOne(student)

        //改:studentDao.update(student)

        //查:List<Student> all = studentDao.getAll()
    }

——通過(guò)上面例子可以簡(jiǎn)單的使用room,不過(guò)不能滿足大部分情況闻镶。下面介紹常用方法

三甚脉、進(jìn)階

(1)有關(guān)表的操作

1. @Entity

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Entity {
    /**
     * 定義表名  默認(rèn)使用類名
     */
    String tableName() default "";

    /**
     * 定義索引
     */
    Index[] indices() default {};

    /**
     * 聲明是否繼承父類索引 默認(rèn)false
     */
    boolean inheritSuperIndices() default false;

    /**
     * 定義主鍵
     */
    String[] primaryKeys() default {};

    /**
     * 定義外鍵
     */
    ForeignKey[] foreignKeys() default {};
}

——通過(guò)該注釋定義一張表。每一張表必須有一個(gè)主鍵铆农。Entity屬性字段表示 參考上面注釋

@Entity(tableName = "students",
        indices = {@Index(value = {"firstName", "address"})},
        inheritSuperIndices = true,
        primaryKeys = {"id", "lastName"},
        foreignKeys = { @ForeignKey(entity = Playlist.class,
                parentColumns = "id",childColumns = "playlistId")})
public class User {
     public int id;
     public String firstName;
     public String lastName;
     public int playlistId;
}

2. @primaryKeys

——除了通過(guò) @Entity(primaryKeys = {"firstName", "lastName"}) 聲明主鍵外牺氨,還可以使用@PrimaryKey注解字段

@Entity
public class Student {
    @PrimaryKey
    int id;
    ...
}

——autoGenerate 可以讓SQLite自動(dòng)生成唯一的id, 默認(rèn)為false

@PrimaryKey(autoGenerate = true)
int id;

3. @ColumnInfo

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ColumnInfo {
    /**
     * 定義列名 默認(rèn)為字段名
     */
    String name() default INHERIT_FIELD_NAME;
    String INHERIT_FIELD_NAME = "[field-name]";
    /**
     * 定義列的類型  默認(rèn)使用 UNDEFINED
     */
    @SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED; 
    /**
     * 列的使用類型
     */
    int UNDEFINED = 1;
    int TEXT = 2;
    int INTEGER = 3;
    int REAL = 4;
    int BLOB = 5;
    @IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
    @interface SQLiteTypeAffinity {
    }
    /**
     * 定義索引
     */
    boolean index() default false;

    /**
     * 定義列的排列順序 默認(rèn)使用 UNSPECIFIED
     */
    @Collate int collate() default UNSPECIFIED;
    /**
     * 列的排列順序常量
     */
    int UNSPECIFIED = 1;
    int BINARY = 2;
    int NOCASE = 3;
    int RTRIM = 4;
    @RequiresApi(21)
    int LOCALIZED = 5;
    @RequiresApi(21)
    int UNICODE = 6;
    @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM, LOCALIZED, UNICODE})
    @interface Collate {
    }
}

——通過(guò)該屬性定義表中的一個(gè)列,ColumnInfo屬性字段表示 參考上面注釋

@Entity
public class Student {
    @PrimaryKey(autoGenerate = true)
    int id;
    @ColumnInfo(name = "names",typeAffinity = TEXT,index = true,collate = UNICODE)
    String name;
    ...
}

4. @Ignore

——如果一個(gè)實(shí)體有您不想持久化的字段波闹,您可以使用@Ignore注釋

@Entity
public class User {
    @PrimaryKey 
    int id; 
    @Ignore 
    String name;
}

(2)對(duì)象之間的關(guān)系

——Room如何處理對(duì)象間 嵌套對(duì)象酝豪,一對(duì)多,多對(duì)多 關(guān)系簡(jiǎn)單介紹

1.Room中使用嵌套對(duì)象(將一類加到另一個(gè)類中)

使用@Embedded注釋 引入需要嵌套進(jìn)來(lái)的對(duì)象精堕。然后孵淘,可以像查詢其他各個(gè)列一樣查詢嵌套字段

    public class Address {
        public String street;
        public String state;
        public String city;

        @ColumnInfo(name = "post_code") public int postCode;
    }

    @Entity
    public class User {
        @PrimaryKey public int id;

        public String firstName;

        @Embedded public Address address;
    }

如果有嵌套有重復(fù)字段可通過(guò)@Embedded 攜帶的 prefix屬性來(lái)定義唯一性

注意:嵌套字段還可以包含其他嵌套字段歹篓。

2.一對(duì)多:如下例子 表示一個(gè)用戶可以擁有多本書(shū)瘫证,使用@ForeignKey定義外鍵約束關(guān)系。使用方式如下

    @Entity(foreignKeys = @ForeignKey(entity = User.class,
                                      parentColumns = "id",
                                      childColumns = "user_id"))
    public class Book {
        @PrimaryKey 
        public int bookId;

        public String title;

        @ColumnInfo(name = "user_id") 
        public int userId;
    }

3.多對(duì)多:舉個(gè)例子 一個(gè)老師有多個(gè)學(xué)生庄撮,而一個(gè)學(xué)生也可以擁有多個(gè)老師背捌。

    @Entity
    public class Teacher {
        @PrimaryKey public int id;
        public String name;
    }

    @Entity
    public class Student {
        @PrimaryKey public int id;
        public String name;
    }

——然后定義一個(gè)中間類包含對(duì)teacher和Student的外鍵引用實(shí)體

    @Entity(primaryKeys = { "teacherId", "studentId" },
            foreignKeys = {
                    @ForeignKey(entity = Teacher.class,
                                parentColumns = "id",
                                childColumns = "teacherId"),
                    @ForeignKey(entity = Student.class,
                                parentColumns = "id",
                                childColumns = "studentId")
                    })
    public class Schools {
        public int teacherId;
        public int studentId;
    }

——這會(huì)生成一個(gè)多對(duì)多關(guān)系模型《此梗可以通過(guò) DAO查詢某個(gè)學(xué)生有哪些老師毡庆,或通過(guò)查詢某個(gè)老師有哪些學(xué)生。

    @Dao
    public interface SchoolsDao {
        @Insert
        void insert(Schools schools);

        @Query("SELECT * FROM teacher " +
               "INNER JOIN shools " +
               "ON teacher.id=schools.teacherId " +
               "WHERE schools.studentId=:studentId")
        List<Teacher> getTeachers(final int studentId);

        @Query("SELECT * FROM student " +
               "INNER JOIN schools " +
               "ON student.id=schools.studentId " +
               "WHERE schools.teacherId=:teacherId")
        List<Student> getTeachers(final int playlistId);
    }

(3)使用Dao訪問(wèn)數(shù)據(jù)庫(kù)

——?jiǎng)?chuàng)建 DAO 方法并使用 @Insert 對(duì)其進(jìn)行注釋時(shí)烙如,Room 會(huì)生成一個(gè)實(shí)現(xiàn)么抗,該實(shí)現(xiàn)在單個(gè)事務(wù)中將所有參數(shù)插入到數(shù)據(jù)庫(kù)中。

@Insert :將數(shù)據(jù)以參數(shù)形式給出的實(shí)體添加到數(shù)據(jù)庫(kù)

    @Dao
    public interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        public void insertUsers(User... users);

        @Insert
        public void insertBothUsers(User user1, User user2);

        @Insert
        public void insertUsersAndFriends(User user, List<User> friends);
    }

@Updata :更新/修改數(shù)據(jù)庫(kù)中以參數(shù)形式給出的一組實(shí)體

    @Dao
    public interface MyDao {
        @Update
        public void updateUsers(User... users);
    }

@Delete :從數(shù)據(jù)庫(kù)中 刪除 一組以參數(shù)形式給出的實(shí)體

    @Dao
    public interface MyDao {
        @Delete
        public void deleteUsers(User... users);
    }

@Query :根據(jù)語(yǔ)法從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù)

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user")
        public User[] loadAllUsers();
    }

——1.將參數(shù)傳遞給查詢

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge")
        public User[] loadAllUsersOlderThan(int minAge);

        @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
        public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

        @Query("SELECT * FROM user WHERE first_name LIKE :search " +
               "OR last_name LIKE :search")
        public List<User> findUserWithName(String search);
    }

——2.返回列的子集:大多數(shù)情況下亚铁,我們只需要獲取實(shí)體的幾個(gè)字段蝇刀,而不是全部。這樣可以節(jié)省資源徘溢、查詢更快吞琐。

可以通過(guò)重新定義返回結(jié)果的對(duì)象(里面的字段都是從原結(jié)果中提取出來(lái)的)如:

去掉常見(jiàn)的id。提取我們所需要的名字信息然爆。

    public class NameTuple {
        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        @NonNull
        public String lastName;
    }

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        public List<NameTuple> loadFullName();
    }

Room 知道該查詢會(huì)返回 first_namelast_name 列的值站粟,并且這些值會(huì)映射到 NameTuple 類的字段。

——3.傳遞參數(shù)的集合:部分查詢可能要求您傳入數(shù)量不定的參數(shù)曾雕。

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public List<NameTuple> loadUsersFromRegions(List<String> regions);
    }

——4.可觀察查詢:執(zhí)行查詢時(shí)奴烙,數(shù)據(jù)發(fā)生變化時(shí)自動(dòng)更新UI。使用 LiveData 類型的返回值翻默。

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
    }

——5.使用 RxJava 進(jìn)行響應(yīng)式查詢

Room 為 RxJava2 類型的返回值提供了以下支持:

  • @Query 方法:Room 支持 Publisher缸沃、Flowable 和 Observable 類型的返回值恰起。
  • @Insert修械、@Update@Delete 方法:Room 2.1.0 及更高版本支持 Completable 、Single<T> 和 Maybe<T> 類型的返回值检盼。

在 app/build.gradle 中添加相關(guān)依賴

    dependencies {
        def room_version = "2.1.0"
        implementation 'androidx.room:room-rxjava2:$room_version'
    }

使用方式:

    @Dao
    public interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        public Flowable<User> loadUserById(int id);

        // Emits the number of users added to the database.
        @Insert
        public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);

        // Makes sure that the operation finishes successfully.
        @Insert
        public Completable insertLargeNumberOfUsers(User... users);

        /* Emits the number of users removed from the database. Always emits at
           least one user. */
        @Delete
        public Single<Integer> deleteUsers(List<User> users);
    }

——6.直接光標(biāo)訪問(wèn):如果應(yīng)用的邏輯需要直接訪問(wèn)返回行肯污,您可以從查詢返回 Cursor 對(duì)象

注意:強(qiáng)烈建議您不要使用 Cursor API,因?yàn)樗鼰o(wú)法保證行是否存在或者行包含哪些值。只有當(dāng)您已具有需要光標(biāo)且無(wú)法輕松重構(gòu)的代碼時(shí)蹦渣,才使用此功能哄芜。

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
        public Cursor loadRawUsersOlderThan(int minAge);
    }

——7.查詢多個(gè)表格:

以下代碼段展示了如何執(zhí)行表格聯(lián)接來(lái)整合兩個(gè)表格的信息:一個(gè)表格包含當(dāng)前借閱圖書(shū)的用戶,另一個(gè)表格包含當(dāng)前處于已被借閱狀態(tài)的圖書(shū)的數(shù)據(jù)柬唯。

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM book " +
               "INNER JOIN loan ON loan.book_id = book.id " +
               "INNER JOIN user ON user.id = loan.user_id " +
               "WHERE user.name LIKE :userName")
       public List<Book> findBooksBorrowedByNameSync(String userName);
    }

——8.使用 Kotlin 協(xié)程編寫(xiě)異步方法

可以將 suspend Kotlin 關(guān)鍵字添加到 DAO 方法认臊,以使用 Kotlin 協(xié)程功能使這些方法成為異步方法。這樣可確保不會(huì)在主線程上執(zhí)行這些方法

    @Dao
    interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        suspend fun insertUsers(vararg users: User)

        @Update
        suspend fun updateUsers(vararg users: User)

        @Delete
        suspend fun deleteUsers(vararg users: User)

        @Query("SELECT * FROM user")
        suspend fun loadAllUsers(): Array<User>
    }

(4)創(chuàng)建視圖

2.1.0 及更高版本的 Room 持久性庫(kù)SQLite 數(shù)據(jù)庫(kù)視圖提供了支持锄奢,從而允許您將查詢封裝到類中失晴。Room 將這些查詢支持的類稱為視圖。

注意:與實(shí)體類似拘央,您可以針對(duì)視圖運(yùn)行 SELECT 語(yǔ)句涂屁。不過(guò),您無(wú)法針對(duì)視圖運(yùn)行 INSERT灰伟、UPDATEDELETE 語(yǔ)句拆又。

要?jiǎng)?chuàng)建視圖,請(qǐng)將 @DatabaseView 注釋添加到類中栏账。將注釋的值設(shè)為類應(yīng)該表示的查詢

    @DatabaseView("SELECT user.id, user.name, user.departmentId," +
                  "department.name AS departmentName FROM user " +
                  "INNER JOIN department ON user.departmentId = department.id")
    public class UserDetail {
        public long id;
        public String name;
        public long departmentId;
        public String departmentName;
    }

    //將視圖與數(shù)據(jù)庫(kù)相關(guān)聯(lián)
    @Database(entities = {User.class}, views = {UserDetail.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }

(5)遷移 Room 數(shù)據(jù)庫(kù) / 數(shù)據(jù)庫(kù)升級(jí)處理

  • 當(dāng)開(kāi)發(fā)者添加和修改數(shù)據(jù)庫(kù)后帖族,用戶更新到應(yīng)用的最新版本時(shí)史辙,不想讓他們丟失所有現(xiàn)有數(shù)據(jù)场钉。可以編寫(xiě)** Migration** 類兰伤,以這種方式保留用戶數(shù)據(jù)了讨。
  • 每個(gè) Migration 類均指定一個(gè) startVersion 和** endVersion**捻激。在運(yùn)行時(shí)前计,Room 會(huì)運(yùn)行每個(gè) Migration 類的 migrate() 方法,以按照正確的順序?qū)?shù)據(jù)庫(kù)遷移到更高版本丈屹。
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
                    + "`name` TEXT, PRIMARY KEY(`id`))");
        }
    };

    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE Book "
                    + " ADD COLUMN pub_year INTEGER");
        }
    };

    Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
            .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

以上常用功能官網(wǎng)都有伶棒,這里只簡(jiǎn)單總結(jié) 歸納介紹肤无。詳情請(qǐng)看官網(wǎng)。

四宛渐、源碼分析

從使用方式一步步分析源碼

(1)創(chuàng)建數(shù)據(jù)庫(kù)實(shí)例

Room.databaseBuilder(activity.getApplicationContext(),RoomDbManager.class, "room_blcs").build()竞漾;

1.Room.databaseBuilder()

/**
 * 創(chuàng)建 RoomDatabase.Builder
 */
public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
        @NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
    //當(dāng)沒(méi)有傳入數(shù)據(jù)庫(kù)名字時(shí)拋出異常
    if (name == null || name.trim().length() == 0) {
        throw new IllegalArgumentException("Cannot build a database with null or empty name."
                + " If you are trying to create an in memory database, use Room"
                + ".inMemoryDatabaseBuilder");
    }
    //分析——> 2
    return new RoomDatabase.Builder<>(context, klass, name);
}

2.RoomDatabase.Builder()

/**
 * 初始化Builder屬性眯搭,并創(chuàng)建了Migration容器
 */
Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
    mContext = context;
    //擴(kuò)展RoomDatabase的抽象類
    mDatabaseClass = klass;
    //數(shù)據(jù)庫(kù)名稱
    mName = name;
    //數(shù)據(jù)庫(kù)日志模式
    mJournalMode = JournalMode.AUTOMATIC;
    //是否更新數(shù)據(jù)庫(kù)
    mRequireMigration = true;
    //分析——> 3
    mMigrationContainer = new MigrationContainer();
}

3.MigrationContainer

/**
 * Migration容器:用于保存Migration,允許查詢Migration兩個(gè)版本之間的內(nèi)容
 * 該實(shí)例用于數(shù)據(jù)庫(kù)版本升級(jí)時(shí)起作用业岁,這里就不詳細(xì)分析數(shù)據(jù)庫(kù)升級(jí)源碼鳞仙,
 * 大致實(shí)現(xiàn)方式:
 * 1.當(dāng)數(shù)據(jù)庫(kù)發(fā)生變化對(duì)版本進(jìn)行升級(jí)時(shí),開(kāi)發(fā)者需要通過(guò)addMigration方法添加Migration實(shí)例笔时,對(duì)升級(jí)進(jìn)行處理棍好,避免數(shù)據(jù)丟失。
 * 2.當(dāng)數(shù)據(jù)庫(kù)升級(jí)后允耿,會(huì)調(diào)用onUpgrade()方法梳玫,該方法通過(guò)findMigrationPath()找到Migration實(shí)例,執(zhí)行數(shù)據(jù)庫(kù)升級(jí)處理右犹。
 * 3.若沒(méi)有添加Migration實(shí)例對(duì)數(shù)據(jù)庫(kù)處理提澎,則room會(huì)執(zhí)行刪除所有表格,再新建所有表格念链。則就會(huì)造成數(shù)據(jù)丟失
 */
public static class MigrationContainer {
    //Migration 容器
    private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
            new SparseArrayCompat<>();

    /**
     * 添加一組Migration到容器中
     */
    public void addMigrations(@NonNull Migration... migrations) {
        for (Migration migration : migrations) {
            addMigration(migration);
        }
    }
    /**
     * 添加單個(gè)Migration到容器中盼忌,如果已經(jīng)存在則覆蓋
     */
    private void addMigration(Migration migration) {
        final int start = migration.startVersion;
        final int end = migration.endVersion;
        SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
        if (targetMap == null) {
            targetMap = new SparseArrayCompat<>();
            mMigrations.put(start, targetMap);
        }
        Migration existing = targetMap.get(end);
        if (existing != null) {
            Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
        }
        targetMap.append(end, migration);
    }

    /**
     * 獲取兩個(gè)版本之間的Migration列表
     */
    @SuppressWarnings("WeakerAccess")
    @Nullable
    public List<Migration> findMigrationPath(int start, int end) {
        if (start == end) {
            return Collections.emptyList();
        }
        boolean migrateUp = end > start;
        List<Migration> result = new ArrayList<>();
        return findUpMigrationPath(result, migrateUp, start, end);
    }

    ...
}

4.RoomDatabase.Builder.build()

/**
 * 創(chuàng)建數(shù)據(jù)庫(kù)實(shí)例并初始化
 * 返回一個(gè)繼承RoomDbManager實(shí)例 ,根據(jù)Demo這里生成的是RoomDbManager_Impl.class
 */
@NonNull
public T build() {
    //這邊省略一些判斷條件掂墓,僅貼出核心代碼
    ...
    //創(chuàng)建FrameworkSQLiteOpenHelperFactory  分析——> 5
    //該實(shí)例實(shí)現(xiàn)了SupportSQLiteOpenHelper.Factory的create方法谦纱。對(duì)數(shù)據(jù)庫(kù)進(jìn)行封裝
    //create方法在->8 會(huì)調(diào)用到
    if (mFactory == null) {
        mFactory = new FrameworkSQLiteOpenHelperFactory();
    }
    //創(chuàng)建數(shù)據(jù)庫(kù)配置類并初始化其屬性
    DatabaseConfiguration configuration =
            new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
                    mCallbacks, mAllowMainThreadQueries,
                    mJournalMode.resolve(mContext),
                    mRequireMigration, mMigrationsNotRequiredFrom);
    //創(chuàng)建數(shù)據(jù)庫(kù)實(shí)例  分析——> 6
    T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
    //初始化數(shù)據(jù)庫(kù)  分析——>7
    db.init(configuration);
    return db;
}

5.FrameworkSQLiteOpenHelperFactory

/**
 * 實(shí)現(xiàn)SupportSQLiteOpenHelper.Factory 并重寫(xiě)了create()方法
 */
public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {

    @Override
    public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
        //到第 8 點(diǎn)才回執(zhí)行到該方法 可以先跳過(guò) 執(zhí)行到在回來(lái)分析
        //創(chuàng)建了FrameworkSQLiteOpenHelper對(duì)象,該對(duì)象持有數(shù)據(jù)庫(kù)實(shí)例
        //分析——> 9
        return new FrameworkSQLiteOpenHelper(
                configuration.context, configuration.name, configuration.callback);
    }
}

6.Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX)

/**
 * 利用反射機(jī)制 創(chuàng)建一個(gè)繼承RoomDbManager.class 的實(shí)例 
 */
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
    final String fullPackage = klass.getPackage().getName();
    String name = klass.getCanonicalName();
    final String postPackageName = fullPackage.isEmpty()
            ? name
            : (name.substring(fullPackage.length() + 1));
    final String implName = postPackageName.replace('.', '_') + suffix;
    try {

        @SuppressWarnings("unchecked")
        //加載指定名稱的類  這里加載的是:RoomDbManager_Impl.class  該類由APT(Android注解處理器)生成 
        final Class<T> aClass = (Class<T>) Class.forName(
                fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
        //創(chuàng)建一個(gè)實(shí)例
        return aClass.newInstance();
    } catch (ClassNotFoundException e) {
        throw new RuntimeException("cannot find implementation for "
                + klass.getCanonicalName() + ". " + implName + " does not exist");
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Cannot access the constructor"
                + klass.getCanonicalName());
    } catch (InstantiationException e) {
        throw new RuntimeException("Failed to create an instance of "
                + klass.getCanonicalName());
    }
}

7.db.init(configuration);

/**
 * 初始化RoomDatabase 屬性
 */
public void init(@NonNull DatabaseConfiguration configuration) {
    //分析——>8   RoomDbManager_Impl類實(shí)現(xiàn)了該方法
    //該方法獲取了FrameworkSQLiteOpenHelper對(duì)象并持有數(shù)據(jù)庫(kù)實(shí)例,
    //完成了數(shù)據(jù)庫(kù)的創(chuàng)建與配置祠乃。
    mOpenHelper = createOpenHelper(configuration);
    ...
}

8.createOpenHelper(configuration)

@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
    //首先 創(chuàng)建RoomOpenHelper.Delegate實(shí)例,該實(shí)例實(shí)現(xiàn)了封裝了RoomOpenHelper方法的一些實(shí)現(xiàn)
    //又創(chuàng)建了RoomOpenHelper實(shí)例嘱支,該實(shí)例持有RoomOpenHelper.Delegate,并調(diào)用Delegate方法完成數(shù)據(jù)庫(kù)的創(chuàng)建  由 分析——>11 得出
    final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
      // 僅貼出部分源碼
      @Override
      public void createAllTables(SupportSQLiteDatabase _db) {
        _db.execSQL("CREATE TABLE IF NOT EXISTS `Student` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `sex` TEXT, `age` INTEGER NOT NULL)");
        _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
        _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"3022583cc4e29bfa9733f59fc1573949\")");
      }
        ...
    }, "3022583cc4e29bfa9733f59fc1573949", "16c81d90557b0b886cda3cb098388f2c");
    //創(chuàng)建SupportSQLiteOpenHelper.Configuration類 持有該 RoomOpenHelper 對(duì)象
    final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
        .name(configuration.name)
        .callback(_openCallback)
        .build();

    //這邊 通過(guò)DatabaseConfiguration 對(duì)象執(zhí)行了 FrameworkSQLiteOpenHelperFactory的create方法 
    //將數(shù)據(jù)庫(kù)的配置信息傳給了SupportSQLiteOpenHelper   分析——>5 
    //通過(guò)分析5 這邊 _helper 其實(shí)就是FrameworkSQLiteOpenHelper對(duì)象 ,該對(duì)象實(shí)現(xiàn)了SupportSQLiteOpenHelper接口
    final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
    return _helper;
}

9.FrameworkSQLiteOpenHelper

/**
 * 該構(gòu)造方法里面 執(zhí)行了createDelegate()創(chuàng)建了數(shù)據(jù)庫(kù)實(shí)例
 * 也就是FrameworkSQLiteOpenHelper持有數(shù)據(jù)庫(kù)OpenHelper 的引用
 */
FrameworkSQLiteOpenHelper(Context context, String name,
        Callback callback) {
    //分析——>10
    mDelegate = createDelegate(context, name, callback);
}

10.createDelegate

/**
 * 該方法主要是創(chuàng)建了創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)實(shí)例OpenHelper贞岭,
 * 并將數(shù)據(jù)庫(kù)的操作方法封裝在FrameworkSQLiteDatabase對(duì)象中
 * 數(shù)據(jù)庫(kù)的建表及其他初始化 交給RoomOpenHelper對(duì)象去實(shí)現(xiàn)。
 */
private OpenHelper createDelegate(Context context, String name, Callback callback) {
    //創(chuàng)建了一個(gè)FrameworkSQLiteDatabase數(shù)組
    final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
    //創(chuàng)建了數(shù)據(jù)庫(kù)實(shí)例   分析——>11
    return new OpenHelper(context, name, dbRef, callback);
}

11.OpenHelper

/**
 * OpenHelper繼承了SQLiteOpenHelper芯侥,這個(gè)就是開(kāi)發(fā)者常見(jiàn)的創(chuàng)建數(shù)據(jù)庫(kù)方式。
 * 通過(guò)創(chuàng)建該實(shí)例就可以操控?cái)?shù)據(jù)庫(kù)了唉工。  這里僅貼出部分方法介紹
 */
static class OpenHelper extends SQLiteOpenHelper {
    OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
            final Callback callback) {
        super(context, name, null, callback.version,
                new DatabaseErrorHandler() {
                    @Override
                    public void onCorruption(SQLiteDatabase dbObj) {
                        FrameworkSQLiteDatabase db = dbRef[0];
                        if (db != null) {
                            callback.onCorruption(db);
                        }
                    }
                });
        //通過(guò) 分析8和5 得出這里callback其實(shí)就是RoomOpenHelper對(duì)象
        mCallback = callback;
        //FrameworkSQLiteDatabase數(shù)組
        mDbRef = dbRef;
    }
    /**
     * 創(chuàng)建FrameworkSQLiteDatabase實(shí)例,該實(shí)例是對(duì)SQLiteDatabase對(duì)象的所有操作進(jìn)行封裝
     * 通過(guò)調(diào)用該實(shí)例就可以對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作
     */
    FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
        FrameworkSQLiteDatabase dbRef = mDbRef[0];
        //判斷該對(duì)象是否已經(jīng)存在
        if (dbRef == null) {
            dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase);
            mDbRef[0] = dbRef;
        }
        return mDbRef[0];
    }
    /**
     * 把建表的操作都交給了mCallback  也就是RoomOpenHelper實(shí)例
     * RoomOpenHelper相當(dāng)于一個(gè)代理類谣膳,把操作都交給了RoomOpenHelper來(lái)實(shí)現(xiàn)。
     */
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        mCallback.onCreate(getWrappedDb(sqLiteDatabase));
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        mMigrated = true;
        mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
    }
}

總結(jié):

  1. 先調(diào)用了Room.databaseBuilder()傳入數(shù)據(jù)庫(kù)的版本信息與名稱花履,創(chuàng)建 RoomDatabase.Builder對(duì)象并建立Migration容器臭挽。
  2. 再調(diào)用了Builder.build()方法,先是創(chuàng)建FrameworkSQLiteOpenHelperFactory對(duì)象實(shí)現(xiàn)了create方法纽帖。create方法內(nèi)部對(duì)數(shù)據(jù)庫(kù)進(jìn)行封裝。
  3. build()方法內(nèi)通過(guò)反射機(jī)制創(chuàng)建了RoomDatabase的子類(RoomDbManager_Impl.class)室囊。該子類由APT生成融撞。
  4. build()方法內(nèi)又創(chuàng)建了數(shù)據(jù)庫(kù)配置類饶火,給RoomDatabase的子類配置信息肤寝。
  5. 配置過(guò)程調(diào)用了RoomDatabase子類的createOpenHelper()方法,該方法創(chuàng)建了RoomOpenHelper實(shí)例刨摩,實(shí)現(xiàn)數(shù)據(jù)庫(kù)的建表語(yǔ)句及其他數(shù)據(jù)庫(kù)操作語(yǔ)句澡刹。
  6. 最終createOpenHelper()方法將RoomOpenHelper實(shí)例傳入到FrameworkSQLiteOpenHelperFactory對(duì)象的create方法完成數(shù)據(jù)庫(kù)的創(chuàng)建于封裝罢浇。

(2)操作表的方法

RoomDbManager room_blcs = Room.databaseBuilder(activity.getApplicationContext(),
            RoomDbManager.class, "room_blcs").build();
room_blcs.getStudentDao()   

通過(guò)(1)源碼分析。build()方法返回的是RoomDbManager的子類RoomDbManager_Impl
而room_blcs.getStudentDao()也就是執(zhí)行了RoomDbManager_Impl.getStudentDao()

1.RoomDbManager_Impl.getStudentDao()

/**
 * 創(chuàng)建了StudentDao_Impl實(shí)例 該實(shí)例由APT生成
 */
@Override
public StudentDao getStudentDao() {
if (_studentDao != null) {
        return _studentDao;
    } else {
      synchronized(this) {
        if(_studentDao == null) {
            //分析 ——>2
          _studentDao = new StudentDao_Impl(this);
        }
        return _studentDao;
      }
    }
}

2.StudentDao_Impl

/**
 * 該類實(shí)現(xiàn)了StudentDao接口的所有方法胞锰,通過(guò)調(diào)用這些方法就可以操作數(shù)據(jù)庫(kù)
 */
public class StudentDao_Impl implements StudentDao {
  private final RoomDatabase __db;

  private final EntityInsertionAdapter __insertionAdapterOfStudent;
  ...
  /**
   * 在構(gòu)造函數(shù)中創(chuàng)建增 刪 改 適配器 來(lái)完成插入刪除更新操作
   */
  public StudentDao_Impl(RoomDatabase __db) {
    this.__db = __db;
    //分析 ——>3
    this.__insertionAdapterOfStudent = new EntityInsertionAdapter<Student>(__db) {
      @Override
      public String createQuery() {
        //由APT生成 交給 EntityInsertionAdapter 執(zhí)行
        return "INSERT OR ABORT INTO `Student`(`id`,`name`,`sex`,`age`) VALUES (nullif(?, 0),?,?,?)";
      }
    ...
  } 

  @Override
  public void insertOne(Student student) {
    __db.beginTransaction();
    try {
    //分析 ——> 4
      __insertionAdapterOfStudent.insert(student);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }
    ...
    這里省略其他相同的實(shí)現(xiàn)方法

/**
 * 查詢方法直接幫我們生成查詢語(yǔ)句 并進(jìn)行數(shù)據(jù)的解析處理。
 */
  @Override
  public List<Student> getAll() {
    final String _sql = "SELECT * FROM Student";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    final Cursor _cursor = __db.query(_statement);
    try {
      final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
      final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
      final int _cursorIndexOfSex = _cursor.getColumnIndexOrThrow("sex");
      final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("age");
      final List<Student> _result = new ArrayList<Student>(_cursor.getCount());
      while(_cursor.moveToNext()) {
        final Student _item;
        _item = new Student();
        final int _tmpId;
        _tmpId = _cursor.getInt(_cursorIndexOfId);
        _item.setId(_tmpId);
        final String _tmpName;
        _tmpName = _cursor.getString(_cursorIndexOfName);
        _item.setName(_tmpName);
        final String _tmpSex;
        _tmpSex = _cursor.getString(_cursorIndexOfSex);
        _item.setSex(_tmpSex);
        final int _tmpAge;
        _tmpAge = _cursor.getInt(_cursorIndexOfAge);
        _item.setAge(_tmpAge);
        _result.add(_item);
      }
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }
}

3.EntityInsertionAdapter

/**
 * 創(chuàng)建了EntityInsertionAdapter 實(shí)例 并持有RoomDatabase的引用
 */
public abstract class EntityInsertionAdapter<T> extends SharedSQLiteStatement {
    ...
    public EntityInsertionAdapter(RoomDatabase database) {
        super(database);
    }
    ...
}
public abstract class SharedSQLiteStatement {
    public SharedSQLiteStatement(RoomDatabase database) {
        mDatabase = database;
    }
}

4.EntityInsertionAdapter.insert(T entity)

public final void insert(T entity) {
    //分析 ——> 5 
    final SupportSQLiteStatement stmt = acquire();
    try {
        bind(stmt, entity);
        stmt.executeInsert();
    } finally {
        release(stmt);
    }
}

5.acquire()

public SupportSQLiteStatement acquire() {
    //是否允許在主線程上執(zhí)行 默認(rèn)為false
    assertNotMainThread();
    // 分析——> 6 
    return getStmt(mLock.compareAndSet(false, true));
}

6.getStmt(boolean canUseCached)
/**
* 創(chuàng)建并執(zhí)行數(shù)據(jù)語(yǔ)句
*/
private SupportSQLiteStatement getStmt(boolean canUseCached) {
    final SupportSQLiteStatement stmt;
    if (canUseCached) {
        if (mStmt == null) {
            // 分析——> 7 
            mStmt = createNewStatement();
        }
        stmt = mStmt;
    } else {
        stmt = createNewStatement();
    }
    return stmt;
}

7.createNewStatement()
/**
* 獲取數(shù)據(jù)庫(kù)語(yǔ)句 并執(zhí)行該語(yǔ)句操作
*/
private SupportSQLiteStatement createNewStatement() {
    //EntityInsertionAdapter實(shí)現(xiàn)了該方法 獲取到數(shù)據(jù)庫(kù)語(yǔ)句
    String query = createQuery();
    // 分析——> 8 
    return mDatabase.compileStatement(query);
}

8.compileStatement()
/**
* 調(diào)用數(shù)據(jù)庫(kù)實(shí)例執(zhí)行語(yǔ)句 
*/
public SupportSQLiteStatement compileStatement(@NonNull String sql) {
    assertNotMainThread();
    return mOpenHelper.getWritableDatabase().compileStatement(sql);
}

這部分比較簡(jiǎn)單就不做太多解釋

總結(jié):

  1. 通過(guò)APT生成了StudentDao_Impl實(shí)現(xiàn)了StudentDao接口的所有關(guān)于數(shù)據(jù)庫(kù)操作的方法吟逝。
  2. StudentDao_Impl持有對(duì)RoomDatabase的引用励稳。而RoomDatabase在分析(1)中已持有數(shù)據(jù)庫(kù)實(shí)例
  3. StudentDao_Impl通過(guò)StudentDao里面的方法通過(guò)注解生成數(shù)據(jù)庫(kù)語(yǔ)句驹尼。并調(diào)用數(shù)據(jù)庫(kù)實(shí)例執(zhí)行語(yǔ)句扶欣。

五、內(nèi)容推薦

六髓绽、項(xiàng)目參考

使用方式Demo放在下面項(xiàng)目 “其他”->"Jetpack架構(gòu)組件"->"Room"

Github / apk下載體驗(yàn)地址

若您發(fā)現(xiàn)文章中存在錯(cuò)誤或不足的地方,希望您能指出株茶!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市僵闯,隨后出現(xiàn)的幾起案子鳖粟,更是在濱河造成了極大的恐慌向图,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件航攒,死亡現(xiàn)場(chǎng)離奇詭異漠畜,居然都是意外死亡蝴悉,警方通過(guò)查閱死者的電腦和手機(jī)拍冠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碟摆,“玉大人断盛,你說(shuō)我怎么就攤上這事钢猛∠岫矗” “怎么了躺翻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵假瞬,是天一觀的道長(zhǎng)脱茉。 經(jīng)常有香客問(wèn)我税肪,道長(zhǎng)锻梳,這世上最難降的妖魔是什么疑枯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮具钥,結(jié)果婚禮上氓拼,老公的妹妹穿的比我還像新娘坏匪。我一直安慰自己适滓,他們只是感情好凭迹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著鱼鸠,像睡著了一般愉昆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倒谷,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音诺苹,去河邊找鬼收奔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滓玖,可吹牛的內(nèi)容都是我干的坪哄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼势篡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼翩肌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起禁悠,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤念祭,失蹤者是張志新(化名)和其女友劉穎站玄,沒(méi)想到半個(gè)月后灾常,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偿警,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年诵原,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贺纲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤块促,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布褥实,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏汁咏。R本人自食惡果不足惜弟灼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望笑诅。 院中可真熱鬧者祖,春花似錦嚷往、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)攻人。三九已至烙博,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留布隔,地道東北人藕咏。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像孽查,于是被迫代替她去往敵國(guó)和親坦喘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盲再,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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