系列文章导航

  1. 【译】Google官方推出的Android架构组件系列文章(一)App架构指南
  2. 【译】Google官方推出的Android架构组件系列文章(二)将Architecture Components引入工程
  3. 【译】Google官方推出的Android架构组件系列文章(三)处理生命周期
  4. 【译】Google官方推出的Android架构组件系列文章(四)LiveData
  5. 【译】Google官方推出的Android架构组件系列文章(五)ViewModel
  6. 【译】Google官方推出的Android架构组件系列文章(六)Room持久化库

原文地址:https://developer.android.com/topic/libraries/architecture/room.html

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

注意:将Room导入工程,请参考将Architecture Components引入工程

需要处理大量结构化数据的应用能从本地持久化数据中受益匪浅。最常见的使用场景是缓存相关的数据。比如,当设备无法访问网络时,用户仍然可以在离线时浏览内容。当设备重新联网后,任何用户发起的内容更改将同步到服务器。

核心框架提供了操作原始SQL内容的内置支持。尽管这些API很强大,但它们相对较低层,需要大量的时间和精力才能使用:

  • 没有对原始SQL查询语句的编译时验证。 当你的数据图变化时,你需要手动更新受影响的SQL查询语句。这个过程可能很耗时,而且容易出错。
  • 你需要使用大量模板代码来进行SQL语句和Java数据对象的转换。

RoomSQLite之上提供一个抽象层,来帮助你处理这些问题。

Room包含三大组件:

  • Database:利用这个组件来创建一个数据库持有者。注解定义一系列实体,类的内容定义一系列DAO。它也是底层连接的主入口点。

    注解类应该是继承RoomDatabase的抽象类。在运行期间,你可以通过调用Room.databaseBuilder()Room.inMemoryDatabaseBuilder()方法获取其实例。

  • Entity:这个组件表示持有数据库行的类。对于每个实体,将会创建一个数据库表来持有他们。你必须通过Database类的entities数组来引用实体类。实体类的中的每个字段除了添加有@Ignore注解外的,都会存放到数据库中。

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

  • DAO: 该组件表示作为数据访问对象(DAO)的类或接口。DAORoom的主要组件,负责定义访问数据库的方法。由@Database注解标注的类必须包含一个无参数且返回使用@Dao注解的类的抽象方法。当在编译生成代码时,Room创建该类的实现。

注意:通过使用DAO类代替查询构建器或者直接查询来访问数据库,你可以分离数据库架构的不同组件。此外,DAO允许你在测试应用时轻松地模拟数据库访问。

这些组件,以及与应用程序其他部分的关系,如图所示:

room_architecture.png

以下代码片段包含一个数据库配置样例,其包含一个实体和一个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实例都相当昂贵,而且很少需要访问多个实例。

实体

当一个类由@Entity注解,并且由@Database注解的entities属性引用,Room将在数据库中为其创建一张数据库表。

默认,Room会为实体类中的每个字段创建一列。如果实体类中包含你不想保存的字段,你可以给他们加上@Ignore注解,如下面代码片段所示:

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

要持久化一个字段,Room必须能够访问它。你可以将字段设置为public,或为它提供gettersetter。如果你使用settergetter,请记住,它们基于RoomJava Bean约定。

主键

每个实体必须定义至少一个字段作为主键。甚至当仅仅只有一个字段时,你仍然需要为该字段加上@PrimaryKey注解。另外,如果你想让Room为实体分配自增ID,你可以设置@PrimaryKey注解的autoGenerate属性。如果实体包含组合主键,你可以使用@Entity注解的primaryKeys属性,如下面的代码片段所示:

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

默认,Room使用类名作为数据库表名。如果你想让表采用不同的名字,设置@Entity注解的tableName属性,如下面的代码片段所示:

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

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

tableName属性类似,Room使用字段名作为数据库中的列名。如果你想要一列采用不同的名字,添加@ColumnInfo注解到字段,如下面代码片段所示:

@Entity(tableName = "users")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("last_name", "address")})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)})class User {    @PrimaryKey    public int id;    @ColumnInfo(name = "first_name")    public String firstName;    @ColumnInfo(name = "last_name")    public String lastName;    @Ignore    Bitmap picture;}

关系

因为SQLite是关系型数据库,你可以指定对象间的关系。尽管大部分的ORM库允许实体对象互相引用,但是Room明确禁止此操作。更多详细信息,请参考附录:实体间无对象引用

尽管你无法直接使用关系,Room仍然允许你定义实体间的外键约束。

例如,假如有另外一个叫做Book的实体,你可以使用@ForeignKey注解来定义它和User实体的关系,如下面代码所示:

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

外键是很强大的,因为它允许你指明当引用的实体更新时应该怎么处理。比如,你可以通过在@ForeignKey注解中包含onDelete=CASCADE,来告诉SQLite如果某个User实例被删除,则删除该用户的所有书。

注意SQLite处理@Insert(onConfilict=REPLACE)作为一组REMOVEREPLACE操作,而不是单个UPDATE操作。这个替换冲突值的方法将会影响到你的外键约束。更多详细信息,请参见SQLite文档的ON_CONFLICT语句。

嵌套对象

有时,你希望将一个实体或POJO表达作为数据库逻辑中的一个整体,即使对象包含了多个字段。在这种情况下,你可以使用@Embeded注解来表示要在表中分为为子字段的对象。然后,你可以像其他单独的列一样查询嵌入的字段。

例如,我们的User类可以包含一个类型为Address的字段,其表示了一个字段组合,包含streetcitystatepostCode。为了将这些组合列单独的存放到表中,将Address字段加上@Embedde注解,如下代码片段所示:

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

这张表示User对象的表将包含以下名字的列:idfirstNamestreetstatecitypost_code

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

如果实体包含了多个同一类型的嵌入字段,你可以通过设置prefix属性来保持每列的唯一性。Room然后将提供的值添加到嵌入对象的每个列名的开头。

数据访问对象(DAO)

Room的主要组件是Dao类。DAO以简洁的方式抽象了对于数据库的访问。

Dao要么是一个接口,要么是一个抽象类。如果它是抽象类,它可以有一个使用RoomDatabase作为唯一参数的可选构造函数。

注意Room不允许在主线程中访问数据库,除非你可以builder上调用allowMainThreadQueries(),因为它可能会长时间锁住UI。异步查询(返回LiveDataRxJava Flowable的查询)则不受此影响,因为它们在有需要时异步运行在后台线程上。

方便的方法

可以使用DAO类来表示多个方便的查询。这篇文章包含几个常用的例子。

插入

当你创建一个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方法接收仅仅一个参数,它可以返回一个long,表示插入项的新的rowId。如果参数是一个数组或集合,它应该返回long []List

更多详情,参见@Insert注解的引用文档,以及SQLite文档的rowId表

更新

Update是一个方便的方法,用于更新数据库中以参数给出的一组实体。它使用与每个实体主键匹配的查询。下面代码片段演示如何定义该方法:

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

虽然通常不是必须的,但你可以让此方法返回一个int值,指示数据库中更新的行数。

删除

Delete是一个方便的方法,用于删除数据库中作为参数给出的实体集。使用主键来查找要删除的实体。下面代码演示如何定义此方法:

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

虽然通常不是必须的,但你可以让此方法返回一个int值,指示数据库中删除的行数。

使用@Query的方法

@Query是DAO类中使用的主要注解。可以让你执行数据库读/写操作。每个@Query方法会在编译时验证,因此如果查询有问题,则会发生编译错误而不是运行时故障。

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

  • 如果仅仅某些字段名匹配,则给出警告
  • 如果没有字段匹配,则给出错误。

简单查询

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

这是一条非常简单的用于加载所有用户的查询。在编译时,Room知道它是查询user表的所有列。如果查询包含语法错误,或者如果user表不存在于数据库,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可能展示仅仅是用户的first name和last name,而不是用户的每个详细信息。通过仅获取应用UI上显示的几列,你可以节省宝贵的资源,并且更快完成查询。

Room允许你从查询中返回任意的java对象,只要结果列集能被映射到返回的对象。比如,你可以创建下面的POJO来拉取用户的first namelast name

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使用查询中访问的表列表来决定是否更新LiveData对象。

RxJava

Room还能从你定义的查询中返回RxJava2PublisherFlowable对象。要使用此功能,请将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);}

警告:非常不鼓励使用Cursor API,因为它无法保证是否行存在,或者行包含什么值。仅当你已经具有期望使用Cursor的代码,并且不能轻易重构时使用。

查询多张表

一些查询可能要求查询多张表来计算结果。Room允许你写任何查询,因此你还可以连接表。此外,如果响应是一个可观察的数据类型,比如FlowableLiveDataRoom会监视查询中引用的所有无效的表。(Furthermore, if the response is an observable data type, such as Flowable or LiveData, Room watches all tables referenced in the query for invalidation)

以下代码片段显示了如何执行表连接,以整合包含借书用户的表和包含目前借出的书信息的表之间的信息。

@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提供对于基本类型和其包装类的内置支持。然后,你有时候使用打算以单一列存放到数据库中的自定义数据类型。为了添加对于这种自定义类型的支持,你可以提供一个TypeConverter,它将负责处理自定义类和Romm可以保存的已知类型之间的转换。

比如,如果我们想要保存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.java}, version = 1)@TypeConverters({Converter.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到不同的作用域,包括单独的实体,DAODAO方法。更多信息,参见@TypeConverters的引用文档。

数据库迁移

当你添加和更改App功能时,你需要修改实体类来反映这些更改。当用户更新到你的应用最新版本时,你不想要他们丢失所有存在的数据,尤其是你无法从远端服务器恢复数据时。

Room允许你编写Migration类来保留用户数据。每个Migration类指明一个startVersionendVersion。在运行时,Room运行每个Migration类的migrate()方法,使用正确的顺序来迁移数据库到最新版本。

警告:如果你没有提供需要的迁移类,Room将会重建数据库,也就意味着你会丢掉数据库中的所有数据。

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会验证模式以确保迁移正确。如果Room发现问题,将还会抛出包含不匹配信息的异常。

测试迁移

迁移并不是简单的写入,并且一旦无法正确写入,可能导致应用程序循环崩溃。为了保持应用程序的稳定性,你应该事先测试迁移。Room提供了一个测试Maven组件来辅助测试过程。然而,要使这个组件工作,你需要导出数据库的模式。

导出数据库模式

汇编后,Room将你的数据库模式信息导出到一个JSON文件中。为了导出模式,在build.gradle文件中设置room.schemaLocation注解处理器属性,如下所示:

build.gradle

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

你可以将导出的JSON文件(代表了你的数据库模式历史)保存到你的版本控制系统中,因为它可以让Room创建旧版本的数据库以进行测试。

为了测试这些迁移,添加Roomandroid.arch.persistence.room:testing组件到测试依赖,然后添加模式位置作为一个asset文件夹,如下所示:

build.gradle

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

测试包提供一个MigrationTestHelper类,该类可以读取这些模式文件。它也是一个JUnit4TestRule类,因此它可以管理创建的数据库。

迁移测试示例如下所示:

@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可以让你在测试过程中轻松模拟数据访问层。这个过程是可能的,因为你的DAO不会泄漏任何数据库的细节。当测试应用的其余部分时,你应该创建DAO类的模拟或假的实例。

有两种方式测试数据库:

  • 在你的宿主开发机上
  • 在一台Android设备上

在宿主机上测试

Room使用SQLite支持库,它提供了与Android Framework类相匹配的接口。该支持允许你传递自定义的支持库实现来测试数据库查询。

即使这些设置能让你的测试运行非常快,也不推荐。因为运行在你的设备上的SQLite版本以及用户设备上的,可能和你宿主机上的版本并不匹配。

在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));    }}

更多信息关于测试数据库迁移,参见测试迁移

附录:实体间无对象引用

将数据库的关系映射到相应的对象模型是一种常见的做法,在服务端可以很好地运行。在服务端当访问时,使用高性能的延迟加载字段。

然而,在客户端,延迟加载是不可行的,因为它可能发生在UI线程上,并且在UI线程上查询磁盘信息会产生显著的性能问题。UI线程有大约16ms的时间来计算以及绘制activity的更新布局,因此即使一个查询仅仅耗费5ms,仍然有可能你的应用会没有时间绘制帧,引发可见的卡顿。更糟糕的是,如果并行运行一个单独的事务,或者设备忙于其他磁盘重任务,则查询可能需要更多时间完成。但是,如果你不使用延迟加载,应用获取比其需要的更多数据,从而造成内存消耗问题。

ORM通常将此决定留给开发人员,以便他们可以基于应用的使用场景来做最好的事情。不幸的是,开发人员通常最终在他们的应用和UI之间共享模型,随着UI随着时间的推移而变化,难以预料和调试的问题出现。

举个例子,使用加载Book对象列表的UI,每个Book对象都有一个Author对象。你可能最初设计你的查询使用延迟加载,这样的话Book实例使用getAuthor()方法来返回作者。第一次调用getAuthor()会调用数据库查询。一段时间后,你会意识到你需要在应用UI上显示作者名字,你可以轻松添加方法调用,如以下代码片段所示:

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

然而,这个看起来无害的修改,会导致Author表在主线程被查询。

如果你频繁的查询作者信息,如果你不再需要数据,后续将会很难更改数据的加载方式,比如你的应用UI不再需要展示有关特定作者的信息的情况。因此,你的应用必须继续加载并不需要显示的数据。如果作者类引用另一个表,例如使用getBooks()方法,这种情况会更糟。

由于这些原因,Room禁止实体类之间的对象引用。相反,你必须显式请求你的应用程序需要的数据。

更多相关文章

  1. Android(安卓)SDK Document 框架导读的翻译和注解[1]
  2. android Handler 机制研究学习笔记
  3. android 打开APN
  4. android studio 新建 activity 失败
  5. Retrofit简要介绍
  6. android 将鼠标右键点击事件改为点击后返回功能
  7. android中怎样声明操作通话记录的权利
  8. android中ContactsContract获取联系人的方法
  9. Android(安卓)MediaProvider数据库模式

随机推荐

  1. cocos2dx android 返回键 Menu键 事件
  2. 如何解决在Android浏览器层固定(position
  3. Android(安卓)以太网(有线网络)开关和状
  4. Android子线程真的不能更新UI么
  5. Android 4.1.2 锁屏(LockScreen)分析
  6. Android 界面开发---控件事件监听器、按
  7. 归纳整理一些工作学习中发现的不错的网站
  8. android 6.0后usb otg设备不显示在文件管
  9. 腾讯轻听模仿流水账(1):16.11.25
  10. Android Studio 视图解析