Room使用七步曲

Room 是一個數據持久化庫,它是  Architecture Component 的一部分。它讓SQLiteDatabase的使用變得簡單,大大減少了重復的代碼,并且把SQL查詢的檢查放在編譯時。

你是否已經有了一個使用了SQLite做持久化的Android項目?如果是的話,你可以遷移到Room!讓我們在7步之內使用Room來重構以前的一個項目。

遷移的 sample app 顯示一個可編輯的用戶名,作為User的一部分存儲在數據庫中。這里我們使用product flavors來展示數據層的不同版本:

兩個flavor使用相同的UI層,使用 Model-View-Presenter 設計模式與UserRepository類一起工作。

在sqlite flavor中,你會看到 UsersDbHelper 和 LocalUserDataSource 類中每個查詢數據庫的方法之間有許多重復的代碼。查詢是在ContentValue的幫助下構造的,而Cursor對象返回的數據是一個字段一個字段的讀取的。這些代碼很容易出錯,比如查詢的時候忘記一個字段或者構造model對象出錯。

讓我們來看看Room是如何提高我們的代碼的。最開始我們只是把sqlite flavor中的類拷貝過來,然后再逐漸修改它們。

第一步 — 更新gradle dependencies

Room是通過Google的 Maven 倉庫來添加的,因此把它添加到根build.gradle文件的倉庫列表中:

allprojects {
    repositories {
        google()
        jcenter()
    }
}

在相同的文件中定義Room庫的版本。目前還是alpha版本,請關注我們的 開發者頁面 了解版本更新。

ext {
   ... 
    roomVersion = '1.0.0-alpha4'
}

在 app/build.gradle 文件中,添加Room的依賴。

dependencies{
 …
implementation        
   “android.arch.persistence.room:runtime:$rootProject.roomVersion”
annotationProcessor 
   “android.arch.persistence.room:compiler:$rootProject.roomVersion”
androidTestImplementation 
   “android.arch.persistence.room:testing:$rootProject.roomVersion”
}

要遷移到Room我們需要增加database的版本,為了保住用戶數據我們需要實現一個 Migration 類。要 測試遷移 ,我們需要導出schema,為此在 app/build.gradle中添加如下代碼:

android {
    defaultConfig {
        ...
       // used by Room, to test migrations
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": 
                                 "$projectDir/schemas".toString()]
            }
        }
    }

    // used by Room, to test migrations
    sourceSets {
        androidTest.assets.srcDirs += 
                           files("$projectDir/schemas".toString())
    }
...

第二步— 把model類更新為Entity

Room為每個用 @Entity 注解了的類創建一張表。類的成員對應表中相應的字段。因此,entity類應該是不包含邏輯的輕量的model類。我們的User類代表數據庫中的數據模型。讓我們修改之,告訴Room它應該基于這個類創建一張表:

  • 用@Entity注解這個類并用tableName屬性設置表的名稱。

  • 使用 @PrimaryKey 注解把一個成員設置為主鍵,這里我們的主鍵是User的ID

  • 使用@ColumnInfo(name = “column_name”) 注解設置成員對應的列名。如果你覺得成員變量名就本身就可以作為列名,也可以不設置。

  • 如果有多個構造方法,使用 @Ignore注解告訴Room哪個用,哪個不用。

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

    @PrimaryKey
    @ColumnInfo(name = "userid")
    private String mId;

    @ColumnInfo(name = "username")
    private String mUserName;

    @ColumnInfo(name = "last_update")
    private Date mDate;

    @Ignore
    public User(String userName) {
        mId = UUID.randomUUID().toString();
        mUserName = userName;
        mDate = new Date(System.currentTimeMillis());
    }

    public User(String id, String userName, Date date) {
        this.mId = id;
        this.mUserName = userName;
        this.mDate = date;
    }
...
}

第三步 — 創建DAO

DAO 負責定義操作數據庫的方法。在SQLite實現的版本中,所有的查詢都是在LocalUserDataSource文件中完成的,里面主要是 使用了Cursor對象來完成查詢的工作。有了Room,我們不再需要Cursor的相關代碼,而只需在UserDao類中使用注解來定義查詢。

比如,當查詢數據庫中所有user的時候,我們只需寫:

@Query(“SELECT * FROM Users”)
List<User> getUsers();

第四步 — 創建數據庫

目前為止,我們已經定義了User表以及對應的查詢,但是我們還沒有創建數據庫來把Room的各個部分聯系在一起。為此,我們需要定義一個繼承了RoomDatabase的抽象類。這個類使用@Database來注解,列出它所包含的Entity以及操作它們的 DAO 。database version 從最初的值按1遞增,因此我們現在的版本應該是2。

@Database(entities = {User.class}, version = 4)
@TypeConverters(DateConverter.class)
public abstract class UsersDatabase extends RoomDatabase {

    private static UsersDatabase INSTANCE;

    public abstract UserDao userDao();

因為我們想保留user數據,所以需要實現一個 Migration 類來告訴Room從版本1遷移到2的過程中需要做些什么。而我們這里database schema沒有被修改,因此什么也不用做,直接提供一個空的實現。

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
   // 因為表沒有發生變化,所以這里什么也不做
    }
};

使用UsersDatabase類創建database對象,定義database的名稱以及migration:

database = Room.databaseBuilder(context.getApplicationContext(),
        UsersDatabase.class, "Sample.db")
        .addMigrations(MIGRATION_1_2)
        .build();

更多關于如何實現數據庫遷移以及底層原理的知識,請看這篇文章:

第五步— 使用Room更新Repository

我們創建了數據庫,User表以及查詢,那么現在是時候使用它們了!我們將在LocalUserDataSource類中使用UserDao方法。

為此我們首先移除constructor的Context,用UserDao替代。當然任何實例化了LocalUserDataSource的地方都需要更新一遍。

其次,LocalUserDataSource中查詢數據庫的方法將用UserDao方法來實現。比如,獲取所有user的方法現在變成了這樣:

public List<User> getUsers() {
   return mUserDao.getUsers();
}

現在到了運行時間!

Room最好的特性之一是如果你在主線程中執行數據庫操作,app將崩潰,顯示下面的信息。

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

把I/O操作從主線程拿掉的可靠方法之一是為數據庫的每一次查詢創建一個新的運行在單獨線程的的Runnable。我們已經在項目的sqlite flavor中使用了這種方法,無需改變。

第六步 — On-device testing

我們創建了一個新類-UserDao 和 UsersDatabase,同時也更改了LocalUserDataSource以使用Room數據庫。現在需要對它們進行測試!

測試 UserDao

要測試UserDao,我們需要創建一個AndroidJUnit4測試類。Room非常酷的一個特性是可以創建一個內存數據庫。這樣就避免了每次測試之后都需要清理數據。

@Before
public void initDb() throws Exception {
    mDatabase = Room.inMemoryDatabaseBuilder(
                           InstrumentationRegistry.getContext(),
                           UsersDatabase.class)
                    .build();
}

每次測試后,我們還必須保證都關閉了數據庫連接。

@After
public void closeDb() throws Exception {
    mDatabase.close();
}

以測試一個User的插入為例,我們先插入user然后再回過頭來檢查是否真的可以從數據庫讀出那條User數據。

@Test
public void insertAndGetUser() {
    // When inserting a new user in the data source
    mDatabase.userDao().insertUser(USER);

    //The user can be retrieved
    List<User> users = mDatabase.userDao().getUsers();
    assertThat(users.size(), is(1));
    User dbUser = users.get(0);
    assertEquals(dbUser.getId(), USER.getId());
    assertEquals(dbUser.getUserName(), USER.getUserName());
}

在LocalUserDataSource中測試UserDao

確保LocalUserDataSource是否仍然能正常工作非常簡單,因為我們已經測試了這個類的行為。我們只需創建一個in-memory database,從它獲取到一個UserDao對象,然后把它作為LocalUserDataSource構造器的一個參數就可以了。

@Before
public void initDb() throws Exception {
    mDatabase = Room.inMemoryDatabaseBuilder(
                           InstrumentationRegistry.getContext(),
                           UsersDatabase.class)
                    .build();
    mDataSource = new LocalUserDataSource(mDatabase.userDao());
}

再次,每次測試之后確保關閉了數據庫的連接。

測試數據庫 migration

在下面這篇文章中我們詳細討論了如何實現數據庫的遷移測試以及MigrationTestHelper的工作原理:

更詳細的例子見 migration sample app

第七步— Cleanup

移除所有被Room替換掉的類和代碼。我們的項目中,只需刪除繼承SQLiteOpenHelper的UsersDbHelper類。

重復易錯的代碼減少了,現在查詢在編譯時檢查,并且所有的東西都是可測試的。簡單的7個步驟就把我們現有的app遷移到了Room。sample app的代碼在 這里

 

來自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0726/8249.html

 

 本文由用戶 hx37rr92 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!