Android官方架構(gòu)組件介紹之Room

持久庫Room

Room在SQLite上提供了一個抽象層钦听,以便在利用SQLite的全部功能的同時使流暢的數(shù)據(jù)庫訪問鲫剿。

需要處理一些重要的結(jié)構(gòu)化數(shù)據(jù)的App通常會從本地的持久數(shù)據(jù)中受益匪淺备韧。最常見的就是使用本地緩存,這樣的話下次如果設備無法聯(lián)網(wǎng)用戶也能瀏覽本地數(shù)據(jù)并進行更改被济。等下次聯(lián)網(wǎng)后再和服務器進行同步拍冠。

Android的Framework為了支持處理原始SQL而提供了SQLite這一強大的API,當時SQLite的API還是相對比較低級缴阎,在使用的時候需要花費大量的經(jīng)歷:

  • 沒有對原始SQL語句的編譯時驗證允瞧,隨著數(shù)據(jù)庫表格的更改,你需要更新相關SQL操作蛮拔,而這個過程可能耗時且容易出錯述暂。
  • 你需要使用大量的樣板代碼在SQL查詢和Java數(shù)據(jù)對象之間進行轉(zhuǎn)換。

Room在為SQL提供抽象層的同時也會考慮到上述的問題建炫。

下面是Room中三個主要組件:

  • Database:此組件用于創(chuàng)建數(shù)據(jù)庫的持有者畦韭,同時在類層級上使用注解來定義一系列的Entity,這些Entity對應著數(shù)據(jù)庫中的表格肛跌。Database類中的方法則用來獲取對應的DAO列表艺配。Database是App層與底層SQLite之間的連接點。
    在應用中要使用此組件的話需要繼承RoomDatabase惋砂。然后通過Room.databaseBuilder()或者Room.inMemoryDatabaseBuilder().獲得該類的實例妒挎。(講到這里其實讀者可以發(fā)現(xiàn),這不就是GreenDao嗎西饵???)酝掩。

  • Entity:此組件的一個實例表示數(shù)據(jù)庫的一行數(shù)據(jù),對于每個Entity類來說眷柔,都會有對應的table被創(chuàng)建期虾。想要這些Entity被創(chuàng)建遏匆,就需要寫在上面Database的注解參數(shù)entities列表中绘面。默認Entity中的所有字段都會拿來創(chuàng)建表,除非在該字段上加上@Ignore注解。

注意:Entity默認都只有空的構(gòu)造方法(如果DAO類可以訪問每個持久化字段)含末,或者構(gòu)造方法的參數(shù)與Entity中的字段的類型和名字相匹配。Room可以使用全字段構(gòu)造方法胸懈,也可以使用部分字段構(gòu)造方法垄开。

  • DAO:這個組件用來表示具有Data Access Object(DAO)功能的類或接口。DAO類是Room的重要組件聋涨,負責定義訪問數(shù)據(jù)庫的方法晾浴。繼承RoomDatabase的類必須包含一個0參數(shù)且返回DAO類的方法。當在編譯期生成代碼的時候牍白,Room會創(chuàng)建實現(xiàn)此DAO的類脊凰。

注意:通過使用DAO類而不是傳統(tǒng)的查詢接口來訪問數(shù)據(jù)庫,可以做到數(shù)據(jù)庫組件的分離茂腥。同時DAO可以在測試APP時支持Mock數(shù)據(jù)狸涌。

下面是其三者和數(shù)據(jù)庫的關系圖:

room architecture

下面看一下簡單的實例,其包含一個Entity最岗,一個Dao以及一個Database帕胆。

User.java

@Entity
public 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

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> 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();
}

當創(chuàng)建完這些文件后,你就可以使用下面的方法來獲得被創(chuàng)建的AppDatabase實例:

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

注意:實例化AppDatabase對象時仑性,應遵循單例設計模式惶楼,因為每個數(shù)據(jù)庫實例都相當昂貴,而且很少需要訪問多個實例诊杆。

Entity

當一個類被添加了@Entity注解并且在Database的@entities被引用歼捐,Room就會為其創(chuàng)建對應的數(shù)據(jù)庫。

默認情況Room會為Entity的每個字段創(chuàng)建對應的數(shù)據(jù)庫列晨汹,如果某個字段不想被創(chuàng)建的話可以使用@Ignore注解:

@Entity
class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

為了Room可以訪問到Entity的字段豹储,你可以將這些字段聲明為public,或者可以給這些字段提供settergetter方法淘这。如果使用setter和getter的話剥扣,需要注意命名規(guī)則。具體參照Java Beans铝穷。

Primary key

每個Entity至少定義一個主鍵钠怯,即使你的Entity只有一個字段也是如此。定義主鍵使用@PrimaryKey曙聂。如果你想讓Room給你的Entity自動生成ID的話晦炊,可以使用@Primary的autoGenerate屬性。如果Entity具有復合主鍵的話,可以使用@Entity的primaryKeys屬性断国,參照下方代碼:

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

    @Ignore
    Bitmap picture;
}

默認情況Room使用Entity的類名來作為數(shù)據(jù)庫的表名贤姆。如果想自定義表名,可以使用@Entity的tableName屬性,如下:

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

注意:SQLite中的表名是大小寫不敏感的稳衬。

與上面的tableName類似霞捡,Room使用Entity的字段名來作為對應的列名,如果想要自定義類名薄疚,可以使用@ColumnInfo注解的name屬性碧信,如下:

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

索引及唯一性

在適當?shù)淖侄紊咸砑铀饕梢约涌鞌?shù)據(jù)庫的訪問速度,要在Entity上添加索引可以使用@Entity的indices屬性输涕,可以添加索引或組合索引:

@Entity(indices = {@Index("firstName"), @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;
}

有些情況下音婶,數(shù)據(jù)庫中的某個字段或字段組合必須是唯一的慨畸,可以通過將@Index的屬性unique設置為ture來實現(xiàn)這一唯一性莱坎。以下代碼用于放置User表中出現(xiàn)姓名組合相同的數(shù)據(jù)。

@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是關系型數(shù)據(jù)庫寸士,所以你可以指定對象之間的關系檐什,但在Room中這是命令禁止的。

雖然在Room中的Entity不能有直接的引用關系弱卡,但Room任然支持在Entity間定義Foreign Key乃正。

例如有個另一個Entity叫做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;
}

外鍵是十分強大的瓮具,它允許你指定引用實體發(fā)生更新是發(fā)生的行為,比如凡人,當需要刪除一個用戶的時候刪除其下所有的圖書名党,只需要為Book的@ForeignKey的屬性onDelete設置為CASCADE

注意:SQLite在處理@Insert(onConflict=REPLACE)的時候挠轴,其實是進行了REMOVEREPLACE兩個操作传睹,而不是單單的UPDATE。此時這里的REMOVE操作可能會影響到對應的外鍵岸晦,

嵌套對象

有時你需要在數(shù)據(jù)庫邏輯中表達一個實體或者Java類欧啤,你可以使用@Embedded注解來實現(xiàn)。具體看例子启上。

例如上面的User實體有一個Address類型的字段邢隧,Address包含了street,city,statepostCode這幾個字段。當生成表格時冈在,Address中的字段將被分別定義為User表中的列名倒慧。如下:

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

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

@Entity
class User {
    @PrimaryKey
    public int id;

    public String firstName;

    @Embedded
    public Address address;
}

這是User表包含以下字段:id, firstName, street, state, citypost_code

注意:以上是可以多重嵌套的。

如果User中嵌套的A和B中存在相同字段迫靖,可以使用@Embedded的prefix屬性院峡,Room會在生成table的時候?qū)refix的值加在列名前。

Data Access Objects (DAOs)

Room中的主要組件就是Dao系宜,DAO以簡潔的方式抽象訪問數(shù)據(jù)庫照激。

Intert

當你創(chuàng)建了一個DAO的方法并加上@Insert注解,Room就會生成一個這個方法是實現(xiàn)盹牧,用于完成此次插入操作:

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

如果插入方法只接受一個參數(shù)的話俩垃,表示僅僅插入一條數(shù)據(jù),這是這個方法可以返回一個long型值汰寓,為新行的id口柳。如果參數(shù)為數(shù)組或集合,則需要返回對應的long[]或者List<Long>有滑。

Update

Update是一個用于更新批量數(shù)據(jù)的實用方法跃闹,它通過主鍵來匹配需要更改數(shù)據(jù)庫數(shù)據(jù):

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

此方法可以返回一個int型數(shù)據(jù),表示此次修改影響到的行數(shù)毛好。

DELETE

Delete用于批量刪除數(shù)據(jù)庫中的數(shù)據(jù)望艺,它也是通過主鍵來匹配需要刪除的數(shù)據(jù):

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

此方法可以返回一個int型數(shù)據(jù),表示此次刪除的行數(shù)肌访。

QUERY

@Query是DAO中的一個重要注解找默,它允許你對數(shù)據(jù)庫進行讀寫操作。每一個@Query方法都會在編譯期做校驗吼驶,所以如果query存在問題的話惩激,你的App編譯將無法通過。

Room同時也會校驗query的返回值蟹演,如果返回結(jié)果和查詢語句中的結(jié)果不匹配风钻,Room將會以一下兩種方式提醒你:

  • 如果有部分字段匹配的話會給出警告。
  • 如果沒有字段匹配轨帜,則給出錯誤提示魄咕。

簡單的查詢

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

這是一個加載所有用戶的查詢,寫法比較簡單蚌父。在編譯期哮兰,Room知道需要查詢User的所有列的值。如果查詢語句包含語法錯誤或者沒有user這個表苟弛,則Room會在編譯時期報錯并給出錯誤信息喝滞。

查詢的參數(shù)傳遞

大部分情況,你需要給查詢語句傳遞特定的參數(shù)膏秫,比如查詢特定年齡段的User右遭,如下:

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

在編譯器處理這個查詢操作的時候,Room會將參數(shù)minAge與:minAge進行綁定。如果此時無法匹配窘哈,則會出現(xiàn)編譯錯誤吹榴。

當然也可以傳遞多個參數(shù),如下:

@Dao
public 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<User> findUserWithName(String search);
}

返回所有列的子集

通常你需要的只是Entity的一部分字段滚婉,例如你的UI只需要先死User的姓名图筹,而不是所有信息。這是為了保證UI的更新速度让腹,你會選擇只查詢姓名這個兩個數(shù)據(jù)远剩。

只要可以將查詢的結(jié)果集映射到返回對象的字段,你就可以返回任何對象骇窍,如下:

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

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

現(xiàn)在你可以在DAO中使用NameTuple了瓜晤。

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

Room能夠返回的first_namelast_name能夠映射到NameTuple,所以Room會生成相應的賦值代碼腹纳。如果返回字段太多或者字段不存在于NameTuple中痢掠,則會發(fā)生編譯出錯。

注意:這里的NameTuple也可以使用@Embedded注解只估。

將集合作為參數(shù)傳遞

有些情況當你查詢時需要傳遞較多的變量志群,例如想要查詢某一地區(qū)集合下的所有用戶,這個集合可能包含幾十個地區(qū)蛔钙,如果用上述簡單的參數(shù)傳遞恐怕夠嗆,現(xiàn)在看看怎么用集合傳遞:

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

Room可以判斷你傳遞的是集合荠医,并在SQL語句中將你的參數(shù)進行展開并填充吁脱。

可監(jiān)聽的查詢

在進行查詢的時候,你希望UI會在查詢結(jié)束后自動更新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);
}

如果你比較熟悉RxJava娃胆,那么很高興告訴你遍希,Room同樣支持返回ExJava2中的PublisherFlowable對象,如下:

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

直接返回Cursor

如果你的App中有部分邏輯需要直接用Cursor的話里烦,可以將DAO的返回值設置為Curso凿蒜,如下:

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

注意:Room很不推薦使用以上Cursor的方法,應為你并不知道Cursor有無數(shù)據(jù)或者包含哪些列胁黑。

多表聯(lián)查

Room支持多表聯(lián)查废封,如果返回數(shù)據(jù)是可監(jiān)聽的,那么Room會監(jiān)聽所有查詢中涉及到的表并及時更新數(shù)據(jù)丧蘸。
下面這個例子是通過內(nèi)聯(lián)查詢某個名字下借閱的圖書:

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

你也可以通過查詢返回純java對象漂洋,如下:

@Dao
public interface MyDao {
   @Query("SELECT user.name AS userName, pet.name AS petName "
          + "FROM user, pet "
          + "WHERE user.id = pet.user_id")
   public LiveData<List<UserPet>> 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;
   }
}

類型轉(zhuǎn)換

Room中的類型轉(zhuǎn)換支持你將某個類的值存儲到某一列中,為此Room提供了TypeConverter這個類用于將自定義類轉(zhuǎn)換成Room所支持的類型。

例如我們想要將Date對象進行存儲刽漂,我們可以這么寫:

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

這樣定義完以后演训,下次Room遇到Date,就能將其轉(zhuǎn)換成Room所支持的Long了贝咙。

下面看看AppDatabase要怎么寫:

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

在AppDatabase上添加TypeConverters注解,并將Converter作為其參數(shù)仇祭。

接著User實體:

@Entity
public class User {
    ...
    private Date birthday;
}

然后是DAO:

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

這里你可以對@TypeConverter做一些范圍限制,比如限制只能在某個Entity颈畸,某個DAO或某個DAO方法中使用乌奇。詳細說明可見@TypeConverter文檔。

數(shù)據(jù)庫迭代升級

當你的App迭代升級的時候眯娱,也需要給你的Entity做迭代升級礁苗,為此你將修改Entity的代碼。當你的用戶升級到最新的App版本的時候徙缴,你可不希望他們丟失老版本的所有數(shù)據(jù)试伙,尤其是在沒有服務器備份的情況下。

Room支持通過寫Migration類來保留用戶數(shù)據(jù)于样。每個Migration都需要指定上一個版本和現(xiàn)在的版本疏叨,在App運行的時候,Room會運行每一個Migration的migrate方法穿剖,并使用正確順序?qū)?shù)據(jù)庫升級到最新版本蚤蔓。

注意:如果你不提供Migration的話,Room會重建數(shù)據(jù)庫而不是升級數(shù)據(jù)庫糊余,這樣的后果就是用戶數(shù)據(jù)會全部都是秀又。

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的驗證昏苏,如驗證有誤,則會拋出異常威沫。

測試升級

Migration并不是簡單的數(shù)據(jù)庫寫入操作贤惯,一旦升級失敗,會對App致命的Crash壹甥。為了保證應用的穩(wěn)定性救巷,應該事先測試Migration,Room提供了一套測試框架句柠,下面我們來簡單學習下浦译。

導出Schema文件

Room需要將你數(shù)據(jù)庫的Schema已Json格式的文件導出棒假,為了導出Schema,需要在build.gradle中做如下配置:

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

你需要將導出的Json文件保存起來精盅,以便Room通過schema文件創(chuàng)建老版數(shù)據(jù)庫進行升級測試帽哑。

為了進行升級測試,需要將android.arch.persistence.room:testing添加到你的測試依賴當中叹俏,然后添加如下配置:

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

測試框架提供了名為MigrationTestHelper的類妻枕,它可以讀取schema文件,這也是一個遵循Junit4測試原則的類粘驰。具體測試代碼如下:

@RunWith(AndroidJUnit4.class)
public class MigrationTest {
    private static final String TEST_DB = "migration-test";

    @Rule
    public MigrationTestHelper helper;

    public MigrationTest() {
        helper = new MigrationTestHelper(InstrumentationRegistry.getContext(),
                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.
    }
}

測試數(shù)據(jù)庫

當你的應用程序運行測試時屡谐,如果你沒有測試數(shù)據(jù)庫本身,則不需要創(chuàng)建完整的數(shù)據(jù)庫蝌数。Room允許你輕松地模擬測試中的數(shù)據(jù)訪問層愕掏。這個過程是可能的,因為您的DAO不會泄露您的數(shù)據(jù)庫的任何細節(jié)顶伞。測試其余的應用程序時饵撑,應該創(chuàng)建DAO類的模擬或假的實例。

這里推薦在Android設備上編寫JUnit測試唆貌,因為這些測試并不需要UI的支持滑潘,所以這些測試會比UI測試速度更快。

測試代碼如下:

@RunWith(AndroidJUnit4.class)
public class SimpleEntityReadWriteTest {
    private UserDao mUserDao;
    private TestDatabase mDb;

    @Before
    public void createDb() {
        Context context = InstrumentationRegistry.getTargetContext();
        //將數(shù)據(jù)庫建在內(nèi)存中锨咙,可以讓你的測試整體更加一體化语卤,更密閉。
        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<User> byName = mUserDao.findUsersByName("george");
        assertThat(byName.get(0), equalTo(user));
    }
}

補充:禁止Entity之間的相互引用

將數(shù)據(jù)庫中的關系映射到相應的對象模型是一個常見的做法蓖租,在服務器端可以很好地運行粱侣,在訪問它們時,它們可以很方便地加載字段蓖宦。

然而,在客戶端油猫,延遲加載是不可行的稠茂,因為它可能發(fā)生在UI線程上,并且在UI線程中查詢磁盤上的信息會產(chǎn)生顯著的性能問題情妖。UI線程有大約16ms的時間來計算和繪制Activity的更新的布局睬关,所以即使一個查詢只需要5 ms,你的應用程序仍然可能耗盡用于繪制的時間毡证,引起明顯的卡頓电爹。更糟糕的是,如果并行運行單獨的事務料睛,或者設備忙于其他磁盤重的任務丐箩,則查詢可能需要更多時間才能完成摇邦。但是,如果不使用延遲加載屎勘,則應用程序?qū)@取比其需要的更多數(shù)據(jù)施籍,從而產(chǎn)生內(nèi)存消耗問題。

ORM通常將此決定留給開發(fā)人員概漱,以便他們可以為應用程序的用例做最好的事情丑慎。不幸的是,開發(fā)人員不會在他們的應用程序和UI之間共享模型瓤摧。UI隨著時間的推移而變化竿裂,難以預料和調(diào)試的問題會不斷出現(xiàn)。

例如照弥,使用加載Book對象列表的UI為例腻异,每本書都有一個Author對象。你可能最初設計你的查詢時使用延遲加載产喉,以便Book的實例使用getAuthor()方法來返回作者捂掰。一段時間后,你意識到需要在應用中顯示作者姓名曾沈。你可以輕松添加方法調(diào)用这嚣,如以下代碼片段所示:

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

就這么一個簡單的操作,導致了在主線程中訪問數(shù)據(jù)庫塞俱。如果Author用引用了另一張表姐帚,那情況可能更糟糕。如果需求變化障涯,這個界面不在需要作者姓名罐旗,那么你的代碼可能會做無畏的延遲加載。

基于以上原因唯蝶,Room禁止Entity之間的引用九秀,如果需要加載相關數(shù)據(jù),可以使用顯示的方法去加載粘我。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鼓蜒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子征字,更是在濱河造成了極大的恐慌都弹,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匙姜,死亡現(xiàn)場離奇詭異畅厢,居然都是意外死亡,警方通過查閱死者的電腦和手機氮昧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門框杜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浦楣,“玉大人,你說我怎么就攤上這事霸琴〗氛瘢” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵梧乘,是天一觀的道長澎迎。 經(jīng)常有香客問我,道長选调,這世上最難降的妖魔是什么夹供? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮仁堪,結(jié)果婚禮上哮洽,老公的妹妹穿的比我還像新娘。我一直安慰自己弦聂,他們只是感情好鸟辅,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著莺葫,像睡著了一般匪凉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捺檬,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天再层,我揣著相機與錄音,去河邊找鬼堡纬。 笑死聂受,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的烤镐。 我是一名探鬼主播蛋济,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炮叶!你這毒婦竟也來了瘫俊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤悴灵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骂蓖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體积瞒,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年登下,在試婚紗的時候發(fā)現(xiàn)自己被綠了茫孔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叮喳。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缰贝,靈堂內(nèi)的尸體忽然破棺而出馍悟,到底是詐尸還是另有隱情,我是刑警寧澤剩晴,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布锣咒,位于F島的核電站,受9級特大地震影響赞弥,放射性物質(zhì)發(fā)生泄漏毅整。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一绽左、第九天 我趴在偏房一處隱蔽的房頂上張望悼嫉。 院中可真熱鬧,春花似錦拼窥、人聲如沸戏蔑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽总棵。三九已至,卻和暖如春房交,著一層夾襖步出監(jiān)牢的瞬間彻舰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工候味, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刃唤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓白群,卻偏偏與公主長得像尚胞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子帜慢,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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