Jetpack系列之Room

Room使用小记

Posted by XYH on November 8, 2018

本文记录Android JetpackArchitecture部分的Room的使用。

Room概述

Room是基于Sqlite上,并在其基础上提供了一个抽象层,更加方便的使用数据库。

Room的架构图:

Room Architecture

Room包含3个核心类。

代码示例:

User.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity(tableName = "user")//tableName可省略
data class User(
        val name: String,
        val age: Int
) {
    @PrimaryKey(autoGenerate = true)
    var id: Long? = null

    @Ignore
    var list: ArrayList<String>? = null

    var createDate = Date()

    var gender: Int? = null
}

UserDao.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun addUser(user: User)

    @Delete
    fun deleteUser(user: User)

    @Query("DELETE FROM user")
    fun deleteAllUser()

    @Update
    fun updateUser(user: User)

    @Query("SELECT * FROM user")
    fun queryAllUsers(): LiveData<List<User>>

    @Query("SELECT * FROM user WHERE id=:id")
    fun querySpecifiedUser(id: Long): Flowable<User>

    @Query("DELETE FROM user WHERE id=:id")
    fun deleteSpecifiedUser(id: Long)
}

AppDatabase.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Database(entities = [User::class], version = 1, exportSchema = false)
@TypeConverters(ListConverter::class, DateConverter::class)
abstract class AppDatabase : RoomDatabase() {
    companion object {

        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase =
                INSTANCE ?: synchronized(this) {
                    INSTANCE
                            ?: buildDatabase(context).also { INSTANCE = it }
                }

        private fun buildDatabase(context: Context): AppDatabase {
            val dbDir = File(Environment.getExternalStorageDirectory(), "GoogleRoomDatabase")
            if (dbDir.exists()) {
                dbDir.mkdir()
            }
            val dbFile = File(dbDir, "Sample.db")
            return Room.databaseBuilder(context.applicationContext,
                    AppDatabase::class.java, dbFile.absolutePath)
                    .build()
        }
    }
    
    abstract fun userDao(): UserDao
}

1
2
3
4
5
@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun addUser(user: User)
}
1
2
val userDao = AppDatabase.getInstance(this).userDao()
userDao.addUser(user)

1
2
3
4
5
@Dao
interface UserDao {
    @Delete
    fun deleteUser(user: User)
}
1
2
val userDao = AppDatabase.getInstance(this).userDao()
userDao.deleteUser(user)

1
2
3
4
5
@Dao
interface UserDao {
    @Update
    fun updateUser(user: User)
}
1
2
val userDao = AppDatabase.getInstance(this).userDao()
userDao.updateUser(user)

1
2
3
4
5
6
7
8
@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun queryUsers(): Flowable<List<User>>

    @Query("SELECT * FROM user WHERE name=:name")
    fun querySameUser(name: String): List<User>
}
1
2
3
4
5
6
7
8
9
val userDao = AppDatabase.getInstance(this).userDao()
userDao.queryUsers()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({ userList ->
                // userList here
            }, {
                //TODO handle error
            })

结合LiveData,ViewModel

RoomDemo

Demo地址

Room常见问题

如何添加非基础数据类型的数据

以List跟Date为例:

1,声明converter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ListConverter {

    @TypeConverter
    fun listToString(list: ArrayList<String>): String {
        return Gson().toJson(list)
    }

    @TypeConverter
    fun stringToList(s: String): ArrayList<String> {
        val token = object : TypeToken<ArrayList<String>>() {}.type
        return Gson().fromJson(s, token)
    }
}

class DateConverter {

    @TypeConverter
    fun toDate(timestamp: Long?): Date? {
        return if (timestamp == null) null else Date(timestamp)
    }

    @TypeConverter
    fun toTimestamp(date: Date?): Long? {
        return date?.time
    }
}

2,在Database声明converter注解

1
2
3
4
@TypeConverters(ListConverter::class, DateConverter::class)
abstract class AppDatabase : RoomDatabase() {
    xxx
}

Cannot access database on the main thread since it may potentially lock the UI for a long period of time

Room的操作默认不允许在主线程进行。 例如下面这段代码就会抛出上述异常:

1
2
 val userDao = AppDatabase.getInstance(this).userDao()
 userDao.addUser(User("Anna",18))

上述操作不能在主线程进行,如果使用了RxJava则可以这么修改:

1
2
3
4
5
6
7
    val userDao = AppDatabase.getInstance(this).userDao()
    Completable.fromAction { userDao.addUser(User("Anna", 18)) }
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe {
                       // insert success
                    }

或者使用其他异步的方式进行。

如果头铁的话,可以这么设置,就能在主线程进行数据库的操作。 初始化Room的时候进行以下设置。

1
2
3
4
Room.databaseBuilder(context.applicationContext,
                    AppDatabase::class.java, dbFile.absolutePath)
                    .allowMainThreadQueries()
                    .build()

Migration

在进行Room数据库发生改动的时候需要进行Migration的操作。 给user增加一个字段gender:Int

1,声明Migration

1
2
3
4
5
6
7
8
class AppRoomMigration(startVersion: Int, endVersion: Int) : Migration(startVersion, endVersion) {
    override fun migrate(database: SupportSQLiteDatabase) {
        //版本1迁移到版本2
        if (startVersion == 1 && endVersion == 2) {
            database.execSQL("ALTER TABLE user ADD COLUMN gender Int")
        }
    }
}

2,添加Migration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//版本更改
@Database(entities = [User::class], version = 2, exportSchema = false)
@TypeConverters(ListConverter::class, DateConverter::class)
abstract class AppDatabase : RoomDatabase() {
    companion object {

        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase =
                INSTANCE ?: synchronized(this) {
                    INSTANCE
                            ?: buildDatabase(context).also { INSTANCE = it }
                }

        private fun buildDatabase(context: Context): AppDatabase {
            val dbDir = File(Environment.getExternalStorageDirectory(), "GoogleRoomDatabase")
            if (dbDir.exists()) {
                dbDir.mkdir()
            }

            val dbFile = File(dbDir, "Sample.db")
            return Room.databaseBuilder(context.applicationContext,
                    AppDatabase::class.java, dbFile.absolutePath)
                    //添加Migration
                    .addMigrations(AppRoomMigration(1, 2))
                    .build()
        }

    }

    abstract fun userDao(): UserDao
}

也可以跨版本进行迁移,代码同上。