Android架构组件-App架构指南

Android架构组件-WorkManager

Android架构组件- Room数据库的使用

Android架构组件-Lifecycle

Android架构组件-Paging库的使用


版权声明:本文为博主原创文章,欢迎大家转载!

转载请标明出处: http://blog.csdn.net/guiying712/article/details/81183653 ,本文出自:【张华洋的博客】


  • 使用Room将数据保存在本地数据库中
  • 使用Room实体定义数据
    • 使用主键
    • 注解索引和唯一性
    • 定义对象之间的关系
    • 创建嵌套对象
  • 使用Room DAO访问数据
    • 定义方法
    • 查询
      • 简单查询
      • 传递参数查询
      • 返回列的子集
      • 传递一组参数
      • 可观察的查询
      • 使用RxJava进行响应型查询
      • 直接光标访问
      • 查询多个表
  • 迁移Room数据库
    • 测试迁移
    • 导出 Schemas
  • 测试Room数据库
    • 在Android设备上测试
    • 在主机上测试
  • 使用Room引用复合数据
    • 使用类型转换器
    • Room不允许对象引用的原因

使用Room将数据保存在本地数据库中

Room 在 SQLite 之上提供了一个抽象层,以便在利用 SQLite 全部功能的同时可以流畅的访问数据库。

最常见的用例是缓存有用的数据片段,当设备无法访问网络时,用户仍然可以离线浏览该内容,然后在设备重新联网后,任何用户发起的内容更改都会同步到服务器。

由于Room会为我们解决这些问题,因此 Google 强烈建议 我们使用 Room 而不是 SQLite。

Room 主要有3个部分:

  • Database: 包含数据库持有者,并作为应用程序持久关系数据的基础连接的主要访问点。

    使用 @Database 注解的类应满足以下条件:

    • 是一个继承自 RoomDatabase 的抽象类。

    • 在注解中包括与数据库关联的实体列表。

    • 包含一个没有参数的抽象方法,并返回一个带注解 @Dao 的类 。

    在运行时,我们可以通过调用 Room.databaseBuilder()或 Room.inMemoryDatabaseBuilder() 来获取 Database 的实例。

  • Entity:表示数据库中的表。

  • DAO:包含用于访问数据库的方法。

这些组件以及它们与应用程序其余部分的关系如图1所示:

Android架构组件- Room数据库的使用_第1张图片
图1.Room 架构图

以下代码段包含具有一个实体和一个 DAO 的示例数据库配置:

User.java

@Entitypublic class User {    @PrimaryKey    private int uid;    @ColumnInfo(name = "first_name")    private String firstName;    @ColumnInfo(name = "last_name")    private String lastName;    // Getters and setters are ignored for brevity,    // but they're required for Room to work.}

UserDao.java

@Daopublic interface UserDao {    @Query("SELECT * FROM user")    List getAll();    @Query("SELECT * FROM user WHERE uid IN (:userIds)")    List loadAllByIds(int[] userIds);    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " + "last_name LIKE :last LIMIT 1")    User findByName(String first, String last);    @Insert    void insertAll(User... users);    @Delete    void delete(User user);}

AppDatabase.java

@Database(entities = {User.class}, version = 1)public abstract class AppDatabase extends RoomDatabase {    public abstract UserDao userDao();}

创建上述文件后,我们将使用以下代码获取已创建数据库的实例:

AppDatabase db = Room.databaseBuilder(getApplicationContext(),        AppDatabase.class, "database-name").build();

注意:在实例化AppDatabase对象时应遵循单例设计模式 ,因为创建每个 RoomDatabase 实例的代价都是相当高昂的,并且我们很少需要访问多个实例。

使用Room实体定义数据


使用 Room 持久化库时,可以将关联字段集定义为实体。对于每个实体,在关联的 Database 对象中创建一个表来保存这些项。

注意:要在应用程序中使用实体, 先将 Architecture Components 添加到应用程序的 build.gradle 文件中。

默认情况下,Room 会为实体中定义的每个字段创建一列,如果实体中有我们不想保留的字段,则可以使用 @Ignore 注解它们。我们必须通过 Database 类中的 entities 数组来引用实体类。

以下代码显示了如何定义实体:

@Entitypublic class User {    @PrimaryKey    public int id;    public String firstName;    public String lastName;    @Ignore    Bitmap picture;}

为了持久化一个字段,Room 必须能够访问它。我们可以将字段设为 public,也可以为其提供 getter和setter,如果使用getter和setter方法,它们要基于Room中的JavaBeans约定。

注意:实体类可以有一个空构造函数(如果相应的 DAO类可以访问每个持久化字段),或者一个构造函数,其参数包含与实体中字段匹配的类型和名称。Room也可以使用完整或部分构造函数,例如只接收某些字段的构造函数。

使用主键


每个实体必须至少定义一个字段作为主键。即使只有一个字段,我们仍需要使用 @PrimaryKey 注解该字段 。另外,如果想要Room 自动分配实体的 ID,我们可以设置 @PrimaryKey 的 autoGenerate 属性。如果实体具有复合主键,则可以使用注解 @Entity 的 primaryKeys 属性,如以下代码所示:

@Entity(primaryKeys = {"firstName", "lastName"})public class User {    public String firstName;    public String lastName;    @Ignore    Bitmap picture;}

默认情况下,Room使用类名作为数据库表名。如果希望表具有不同的名称,则设置注解 @Entity 的 tableName 属性,如以下代码所示:

@Entity(tableName = "users")public class User {    ...}

警告: SQLite 中的表名称不区分大小写。

与 tableName 属性类似 ,Room 使用字段名称作为数据库中的列名称,如果希望列具有不同的名称,请将注解@ColumnInfo 添加到字段中,如以下代码所示:

@Entity(tableName = "users")public class User {    @PrimaryKey    public int id;    @ColumnInfo(name = "first_name")    public String firstName;    @ColumnInfo(name = "last_name")    public String lastName;    @Ignore    Bitmap picture;}

注解索引和唯一性


根据访问数据的方式,我们可能希望索引数据库中的某些字段以加快查询速度。要向实体类添加索引,请在注解 @Entity 中包含 indices 属性 ,列出要包含在索引或复合索引中的列的名称。以下代码显示了此注解过程:

@Entity(indices = {@Index("name"),        @Index(value = {"last_name", "address"})})public class User {    @PrimaryKey    public int id;    public String firstName;    public String address;    @ColumnInfo(name = "last_name")    public String lastName;    @Ignore    Bitmap picture;}

有时数据库中的某些字段或字段组必须是唯一的。我们可以通过将注解 @Index 的 unique 属性设置为 true来强制实施唯一性 。以下代码示例可防止一个表包含两个行,这些行包含与 firstNamelastName 列相同的值集:

@Entity(indices = {@Index(value = {"first_name", "last_name"},        unique = true)})public class User {    @PrimaryKey    public int id;    @ColumnInfo(name = "first_name")    public String firstName;    @ColumnInfo(name = "last_name")    public String lastName;    @Ignore    Bitmap picture;}

定义对象之间的关系


由于 SQLite 是关系型数据库,因此我们可以指定对象之间的关系。尽管大多数对象关系映射库允许实体对象相互引用,但Room 明确禁止这样做。要想了解此决策背后的技术原理,直接翻到本文的最后一小节查看。

即使我们不能使用直接关系,Room 仍允许我们在实体之间定义外键约束。

例如,有其他实体类要调用 Book,我们可以使用注解 @ForeignKey 定义 User实体与它的关系,如以下代码所示:

@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;}

外键非常强大,因为它允许我们指定:引用的实体类更新时发生的情况。例如,如果要告诉SQLite删除 user 的所有books,可以通过在注解 @ForeignKey 中包含 onDelete = CASCADE 来删除相应的User实例 。

注意:SQLite处理 @Insert(onConflict = REPLACE) 作为一组REMOVEREPLACE 操作,而不是单个的 UPDATE 操作。这种替换冲突值的方法可能会影响外键约束。

创建嵌套对象


有时,我们希望将实体或普通旧Java对象(PO​​JO)表示为数据库逻辑中的一个组合体,也就是让该对象包含多个字段。在这些情况下,我们可以使用注解 @Embedded 来表示要分解到表中子字段的对象。然后我们就可以像查找其他单个列一样查询嵌入字段。

例如,User类可以包含一个Address类型的字段,它由命名为 streetcitystate,和 postCode 的字段组成,要将组合列分别存储在表中,可以在User类中包含一个带注解@Embedded 的Address字段 ,如以下代码所示:

public class Address {    public String street;    public String state;    public String city;    @ColumnInfo(name = "post_code")    public int postCode;}@Entitypublic class User {    @PrimaryKey    public int id;    public String firstName;    @Embedded    public Address address;}

此 table 表示User 对象包含以下名称的列:idfirstNamestreetstatecity,和 post_code

注意:嵌入字段还可以包含其他嵌入字段。

如果实体具有多个相同类型的嵌入字段,则可以通过设置 prefix 属性使每个列保持唯一 。然后 Room 将提供的值添加到嵌入对象中每个列名称的开头。

使用Room DAO访问数据


要使用 Room 持久库访问应用程序的数据 ,我们需要使用数据访问对象 或 DAO。这组 Dao 对象是构成Room的主要组件,因为每个 DAO 都包含抽象访问我们应用程序数据库的方法。

通过使用 DAO 类而不是查询构造器或直接查询来访问数据库,可以将数据库结构分成不同组件。此外,DAO允许在测试应用程序时轻松模拟数据库访问 。

DAO 可以是接口,也可以是抽象类。如果它是一个抽象类,它可以选择使用一个构造函数,该构造函数将 RoomDatabase 作为唯一参数。Room在编译期创建每个DAO 的实现。

注意:Room不支持主线程上的数据库访问,因为它可能会长时间锁定 UI, 除非我们调用 RoomDatabase.Builder 的 allowMainThreadQueries() 。异步查询 - 返回 LiveData or Flowable 实例的查询不受此规则影响,因为它们在需要时会在后台线程上运行异步查询。

定义方法


创建 DAO 方法并使用 @Insert 进行注解,Room会生成一个实现,该实现在单个事务中将所有参数插入到数据库中。

以下代码段显示了几个示例查询:

@Daopublic 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 friends);}

如果 @Insert 方法只接收 1 个参数,则它可以返回一个 long,这是插入条目的新 rowId,如果参数是数组或集合,则应返回 long[]List 替代。

Update 方法可以在数据库中方便的修改一组给定为参数的实体,它使用与每个实体主键匹配的查询。

以下代码段演示了如何定义此方法:

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

虽然通常没有必要,但我们可以让此方法返回一个int值,表示数据库中更新的行数。

Delete 方法可以在数据库中方便的移除一组给定为参数的实体。它使用主键来查找要删除的实体。

以下代码段演示了如何定义此方法:

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

虽然通常没有必要,但我们可以让此方法返回一个int值,表示从数据库中删除的行数。

查询


@Query 是DAO类中使用的主要注解,它允许我们对数据库执行 读/写 操作。每个 @Query 方法都在编译时进行验证,因此如果查询出现问题,则会发生编译错误而不是运行时失败。

Room 还会验证查询的返回值,如果返回的对象中的字段名称与查询响应中的相应列名称不匹配时,Room会以下列两种方式之一提醒:

  • 如果只有一些字段名称匹配,它会发出警告。
  • 如果没有字段名称匹配,则会出错。

简单查询

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

这是一个非常简单的查询,用来加载所有用户。在编译时,Room知道它正在查询用户表中的所有列。如果查询包含语法错误,或者数据库中不存在用户表,则在应用程序编译时,Room会显示包含相应消息的错误。

传递参数查询

大多数情况下,我们需要将参数传递给查询以执行过滤操作,例如仅显示年龄超过特定年龄的用户。要完成此任务,我们需要在 Room 注解中使用方法参数,如以下代码段所示:

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

在编译时处理此查询,Room 会将 :minAge 绑定参数与 minAge 方法参数匹配,Room 使用参数名称执行匹配。如果存在不匹配,在应用编译时会发生错误。

我们还可以在查询中传递多个参数或引用多次,如以下代码段所示:

@Daopublic interface MyDao {    @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 findUserWithName(String search);}

返回列的子集

大多数情况下,我们只需要获得实体的几个字段。例如,UI 可能只显示用户的名字和姓氏,而不是每个用户的详细信息。通过仅获取应用程序 UI 中显示的列,可以节省宝贵的资源,并且可以更快地完成查询。

Room 允许从查询结果中返回任何基于Java 的对象,只要结果 的集合可以映射到返回的对象中即可。例如,我们可以创建以下普通的基于Java 的 旧对象(PO​​JO)来获取用户的名字和姓氏:

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

现在,我们可以在查询方法中使用此 POJO

@Daopublic interface MyDao {    @Query("SELECT first_name, last_name FROM user")    public List loadFullName();}

Room 了解查询返回 first_namelast_name 列的值,并且这些值可以映射到 NameTuple 类的字段中 。因此,Room 可以生成正确的代码。如果查询返回太多列 或 NameTuple 类中不存在的列,则Room会显示警告。

注意:这些 POJO 也可以使用 @Embedded 注解。

传递一组参数

有些查询可能要求传入可变数量的参数,并且在运行之前不知道参数的确切数量。例如,我们可能希望从区域的子集中检索有关所有用户的信息。Room了解参数何时表示集合,并根据提供的参数数量在运行时自动扩展它。

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

可观察的查询

执行查询时,我们通常希望应用程序的 UI 在数据更改时自动更新。要实现此目的,可以在查询方法描述中使用 LiveData 类型的返回值,Room 会生成所有必要的代码,当数据库更新时以更新 LiveData 。

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

注意:从版本1.0开始,Room 使用查询中访问的 tables 列表来决定是否更新 LiveData 的 实例 。

使用RxJava进行响应型查询

Room 还可以从我们定义的查询中返回 RxJava2 的 Publisher 和 Flowable 对象。要使用此功能,要将 Room 中的android.arch.persistence.room:rxjava2 添加到构建Gradle依赖项中。然后,就可以返回 RxJava2 中定义的类型的对象,如以下代码段所示:

@Daopublic interface MyDao {    @Query("SELECT * from user where id = :id LIMIT 1")    public Flowable loadUserById(int id);}

直接光标访问

如果应用程序的逻辑需要直接访问 返回 行,则可以从查询中返回一个 Cursor 对象,如以下代码段所示:

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

警告:Google 非常不鼓励使用 Cursor API,因为它不保证行是否存在或 行 包含的值。仅当我们已经拥有需要光标的代码且无法轻松重构时才使用此功能。

查询多个表

某些查询可能需要访问多个表来计算结果。Room允许编写任何查询,因此我们也可以连接表。此外,如果响应是可观察的数据类型,例如 LiveData 或 Flowable ,Room 则会监视查询中引用的所有失效的表。

以下代码段显示了如何执行表连接,以合并包含借书用户的表和包含当前借阅书籍数据的表之间的信息:

@Daopublic 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 findBooksBorrowedByNameSync(String userName);}

我们还可以从这些查询中返回 POJO。例如,我们可以编写一个加载用户及其宠物名称的查询,如下所示:

@Daopublic interface MyDao {   @Query("SELECT user.name AS userName, pet.name AS petName "          + "FROM user, pet "          + "WHERE user.id = pet.user_id")   public LiveData> loadUserAndPetNames();   // You can also define this class in a separate file, as long as you add the   // "public" access modifier.   static class UserPet {       public String userName;       public String petName;   }}

迁移Room数据库


名词解释:数据库的 schema 就是数据库对象的集合,这个集合包含了各种对象,如:表、视图、存储过程、索引等

在应用程序中添加和更改功能时,需要修改实体类以反映这些更改。当用户更新到最新版本的应用程序时,我们肯定不希望他们丢失所有的现有数据,尤其是在无法从远程服务器恢复数据时。

Room 持久库允许我们编写 Migration 类以这种方式保存用户数据。每个 Migration 类指定一个 startVersionendVersion 。在运行时,Room 会运行每个 Migration 类的 migrate() 方法,使用正确的顺序将数据库迁移到更高版本。

警告:如果你没有提供必要的迁移,则会重建数据库,这意味着用户将丢失数据库中的所有数据。

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();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 会验证 Schema 以确保正确进行迁移。如果Room发现问题,则会抛出包含不匹配信息的异常。

测试迁移

名词解释:数据库的 schema 就是数据库对象的集合,这个集合包含了各种对象,如:表、视图、存储过程、索引等

迁移并不容易编写,如果无法正确编写它们可能会导致应用程序出现崩溃循环。为了保持应用程序的稳定性,我们应该事先测试迁移。Room 提供测试 Maven 工件以协助此测试过程。但是,要使此工件生效,您需要导出数据库的 Schema。

导出 Schemas

名词解释:数据库的 schema 就是数据库对象的集合,这个集合包含了各种对象,如:表、视图、存储过程、索引等

编译后,Room 会将数据库的 Schema 信息导出到JSON文件中。要导出 Schema ,请在 build.gradle文件中设置room.schemaLocation 注解处理器属性,如以下代码段所示:

build.gradle

android {    ...    defaultConfig {        ...        javaCompileOptions {            annotationProcessorOptions {                arguments = ["room.schemaLocation":                             "$projectDir/schemas".toString()]            }        }    }}

我们应该在版本控制系统中存储导出的JSON文件(表示数据库的 schema 历史记录),因为它允许 Room 创建数据库的旧版本以进行测试。

要测试这些迁移,请将Maven工件 android.arch.persistence.room:testing 从Room 添加到测试依赖项中,并将 schema 位置添加为 asset 文件夹,如以下代码段所示:

build.gradle

android {    ...    sourceSets {        androidTest.assets.srcDirs += files("$projectDir/schemas".toString())    }}

测试包提供了一个 MigrationTestHelper 类,可以读取这些 schema 文件,它还实现了JUnit4 TestRule 接口,因此它可以管理创建的数据库。

以下代码段中显示了迁移测试:

@RunWith(AndroidJUnit4.class)public class MigrationTest {    private static final String TEST_DB = "migration-test";    @Rule    public MigrationTestHelper helper;    public MigrationTest() {        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),                MigrationDb.class.getCanonicalName(),                new FrameworkSQLiteOpenHelperFactory());    }    @Test    public void migrate1To2() throws IOException {        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);        // db has schema version 1. insert some data using SQL queries.        // You cannot use DAO classes because they expect the latest schema.        db.execSQL(...);        // Prepare for the next version.        db.close();        // Re-open the database with version 2 and provide        // MIGRATION_1_2 as the migration process.        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);        // MigrationTestHelper automatically verifies the schema changes,        // but you need to validate that the data was migrated properly.    }}

测试Room数据库


在使用 Room 创建数据库时,验证我们的应用程序的数据库和用户数据的稳定性非常重要 。

有两种方法可以测试 Room 数据库:

  • 在Android设备上。

  • 在我们的主机开发机器上(不推荐)。

注意:在为应用程序运行测试时,Room 允许我们创建 DAO 类的模拟实例。这样,如果我们没有测试数据库本身,则无需创建完整数据库,此功能是可行的,因为 DAO 不会泄漏数据库的任何详细信息。

在Android设备上测试


推荐大家编写能够在Android设备上运行的 JUnit 测试以测试数据库的实现,因为这些测试不需要创建 activity,所以它们的执行速度应该比 UI 测试快。

在设置测试时,您应该创建数据库的内存版本,以使测试更加密集,如以下示例所示:

@RunWith(AndroidJUnit4.class)public class SimpleEntityReadWriteTest {    private UserDao mUserDao;    private TestDatabase mDb;    @Before    public void createDb() {        Context context = InstrumentationRegistry.getTargetContext();        mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();        mUserDao = mDb.getUserDao();    }    @After    public void closeDb() throws IOException {        mDb.close();    }    @Test    public void writeUserAndReadInList() throws Exception {        User user = TestUtil.createUser(3);        user.setName("george");        mUserDao.insert(user);        List byName = mUserDao.findUsersByName("george");        assertThat(byName.get(0), equalTo(user));    }}

在主机上测试


Room 底层使用了 SQLite 支持库,它提供与 Android Framework类中的接口相匹配的接口,因此允许我们传递支持库的自定义实现以测试数据库查询。

注意:即使此设置允许我们快速运行测试,也不建议这样做,因为我们的设备上和用户的设备上运行的 SQLite 版本可能与主机上的版本不匹配。

使用Room引用复合数据


Room提供了在原始类型和装箱类型之间进行转换的功能,但不允许实体之间的对象引用。接下来介绍如何使用类型转换器以及Room不支持对象引用的原因。

使用类型转换器


有时候,应用程序需要使用自定义数据类型,其值要存储在单个数据库列中。要为自定义类型添加此类支持,需要提供一个 TypeConverter,它将自定义类转换为 Room 可以持久化的已知类型。

例如,如果我们想要持久化 Date 的实例,我们可以编写如下 TypeConverter 在数据库中存储等效的 Unix 时间戳:

public class Converters {    @TypeConverter    public static Date fromTimestamp(Long value) {        return value == null ? null : new Date(value);    }    @TypeConverter    public static Long dateToTimestamp(Date date) {        return date == null ? null : date.getTime();    }}

上面的例子中定义了两个函数,一个将 Date 对象转换为 Long 对象,另一个方法执行反转,从 LongDate。由于Room已经知道如何持久化 Long 对象,因此它可以使用此转换器来持久保存 Date 类型的值。

接下来,我们要将注解 @TypeConverters 添加到 AppDatabase 类,以便 Room 可以使用我们为每个实体定义的转换器和 AppDatabase 中的 DAO:

AppDatabase.java

@Database(entities = {User.class}, version = 1)@TypeConverters({Converters.class})public abstract class AppDatabase extends RoomDatabase {    public abstract UserDao userDao();}

使用这些转换器,我们可以在其他查​​询中使用自定义类型,就像使用基本类型一样,如以下代码段所示:

User.java

@Entitypublic class User {    ...    private Date birthday;}

UserDao.java

@Daopublic interface UserDao {    ...    @Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")    List findUsersBornBetweenDates(Date from, Date to);}

我们还可以限制 @TypeConverters 到不同的范围,包括单个实体,DAO和DAO方法。

Room不允许对象引用的原因


关键点: Room 不允许实体类之间的对象引用。相反,我们必须明确请求应用所需的数据。

将数据库中的关系映射到相应的对象模型是一种常见的做法,并且在服务器端非常有效。即使程序在访问字段时加载字段,服务器仍然可以正常运行。

但是,在客户端,这种类型的延迟加载是不可行的,因为它通常发生在UI线程上,并且在UI线程中查询磁盘上的信息会产生严重的性能问题。UI线程通常有大约16毫秒来计算和绘制 Activity 的更新布局,因此即使查询只需要5毫秒,我们的应用程序仍然可能会用完时间来绘制帧,从而导致明显的视觉故障。如果并行运行单独的事务,或者设备正在运行其他磁盘密集型任务,则查询可能需要更多时间才能完成。但是,如果不使用延迟加载,则应用程序会获取超出其需要的数据,从而产生内存消耗问题。

对象关系映射通常会将此决定留给开发人员,以便他们可以为应用程序的用例做最好的事情。开发人员通常决定在他们的应用程序和UI之间共享模型。但是,此解决方案不能很好地扩展,因为随着 UI 的变化,共享模型会产生开发人员难以预测和调试的问题。

例如,考虑加载 一个 Book列表的UI,每个书都有一个Author对象。我们最初可能会设计查询以使用延迟加载,以便Book使用getAuthor()方法返回作者的实例。第一次调用getAuthor()调用查询数据库,过一会后,我们意识到还需要在 UI中显示作者姓名。我们可以轻易添加方法调用,如以下代码段所示:

authorNameTextView.setText(book.getAuthor().getName());

但是,这种看似没问题的变化导致Author在主线程上查询表。

如果提前查询作者信息,则在不再需要该数据时,很难更改数据的加载方式。例如,如果应用的UI不再需要显示Author信息,而我们的应用会继续加载不再显示的数据,从而浪费宝贵的内存空间。如果Author类引用另一个表(如Books),,则应用程序的效率会进一步降低。

要使用 Room 同时引用多个实体,我们需要创建一个包含每个实体的POJO,然后编写一个连接相应表的查询。这种结构良好的模型与 Room 强大的查询验证功能相结合,可以让我们的应用在加载数据时消耗更少的资源,从而提高应用的性能和用户体验。


Android架构组件-App架构指南

Android架构组件-WorkManager

Android架构组件- Room数据库的使用

Android架构组件-Lifecycle

Android架构组件-Paging库的使用

更多相关文章

  1. Android 对象序列化之你不知道的 Serializable
  2. Android 数据库之 SQLiteConnectionPool 源码分析
  3. Android 对象序列化之 Parcelable 取代 Serializable ?
  4. Android 对象序列化之追求完美的 Serial
  5. Android 数据库技术
  6. android rom短信模块最后获取的Cursor字段内容
  7. xml字段含义
  8. Oracle Clob字段过长保存出错改如何解决?
  9. 带你了解CLR是如何创建运行时对象?

随机推荐

  1. php array_unshift()函数详解(实例)
  2. 详解PHP论坛实现系统的思路
  3. 值得一看!高级PHP工程师必备的编码技巧及
  4. 实例讲解php提交表单关闭layer弹窗iframe
  5. 控制反转原则,它和依赖注入有什么联系
  6. 教你使用mixphp打造多进程异步邮件发送
  7. 分析ThinkPHP防止重复提交表单的方法实例
  8. 总结PHP实现提取多维数组指定一列的方法
  9. 【记录】PHP-fpm占CPU 100%修复
  10. PHP实现图片防盗链破解操作方法【解决图