本文记录Android Jetpack中
Architecture
部分的Room的使用。
Room概述
Room是基于Sqlite上,并在其基础上提供了一个抽象层,更加方便的使用数据库。
Room的架构图:
Room包含3个核心类。
- Database:包含数据库的持有类,作为数据库的操作入口。
注解@Database的类需要满足以下条件。
- 抽象类并且继承于RoomDatabase
- @Database注解中包含与数据库关联实体的列表
- 包含一个抽象方法并返回带注释的类 @Dao 通过 Room.databaseBuilder() 或者Room.inMemoryDatabaseBuilder()来获取Database实例。
- Entity:数据库中的表。
- Dao:包含操作数据库的方法。
代码示例:
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
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
}
也可以跨版本进行迁移,代码同上。