使用协程完成本地数据库SQLite操作
封装dao
package com.example.kotlin01.database
import android.app.Application
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.example.kotlin01.dao.UserDao
import com.example.kotlin01.model.User
/**
* 创建数据库,并根据实体类创建表
*/
@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getUserDao(): UserDao
/**
* object关键字通过对象声明来实现Java 中的单实例模式,与普通类不同,对象声明在定义的时候就被创建,
* 无需构造方法。Kotlin中的对象声明被编译成了通过静态字段持有它的单实例的类。
*/
object AppDataBaseSingleClass {
/**
* 以单实例的形式初始化数据 并对外提供 AppDataBase实例
* @param context
* @return
*/
open fun getInstance(application: Application): AppDatabase? {
val db = Room.databaseBuilder(
application.applicationContext,
AppDatabase::class.java, "user-info"
).build()
return db
}
}
}
package com.example.kotlin01.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.example.kotlin01.model.User
/**
* dao层
*/
@Dao
interface UserDao {
// 因为这个方法被标记为了 suspend,Room 将会在保证主线程安全的前提下使用自己的调度器来运行这个查询
@Query("SELECT * FROM user")
suspend fun queryUser(): List<User>
// 因为这个方法被标记为了 suspend,Room 将会在保证主线程安全的前提下使用自己的调度器来运行这个查询
@Insert
suspend fun addUser(user: User)
//因为这个方法被标记为了 suspend,Room 将会在保证主线程安全的前提下使用自己的调度器来运行这个查询
@Delete
suspend fun deleteUser(user: User)
}
package com.example.kotlin01.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
//表名为user
@Entity(tableName = "user")
data class User(
@PrimaryKey val id: Long,
@ColumnInfo(name = "username") val username: String?,
@ColumnInfo(name = "sex") val sex: String?
)
使用viewModelScope+协程
package com.example.kotlin01.viewmodel
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.example.kotlin01.database.AppDatabase
import com.example.kotlin01.model.User
import com.example.kotlin01.repository.UserRepository
import kotlinx.coroutines.launch
//加val就是作为成员变量(val repository: UserRepository)
class UserViewModel constructor(application: Application) : AndroidViewModel(application) {
val repository: UserRepository
init {
repository = UserRepository(
AppDatabase.AppDataBaseSingleClass.getInstance(application)!!
.getUserDao()
)
}
val userLiveData: MutableLiveData<List<User>> = MutableLiveData()
fun queryUser() {
//// suspend 和 resume 使得这个数据库请求是主线程安全的,所以 ViewModel 不需要关心线程安全问题
viewModelScope.launch {
userLiveData.value = repository.queryUser()
}
}
fun addUser(user: User) {
//// suspend 和 resume 使得这个数据库请求是主线程安全的,所以 ViewModel 不需要关心线程安全问题
viewModelScope.launch {
val result = repository.addUser(user)
Log.e("UserViewModel", "addUser: " + result)
}
}
fun deleteUser(user: User) {
//// suspend 和 resume 使得这个数据库请求是主线程安全的,所以 ViewModel 不需要关心线程安全问题
viewModelScope.launch {
val result = repository.deleteUser(user)
Log.e("UserViewModel", "addUser: " + result)
}
}
}
以上我们通过viewModelScope启动协程,在数据库的dao方法中我们使用supend修饰符修饰,表示当前是一个挂起函数,我们不需要额外的Dispatcher线程,因为room内部帮忙调度了线程。因此我们直接使用supend修饰即可,表示当前是一个挂起函数。执行SQL的时候协程会挂起,当执行完毕返回结果的时候,协程会恢复。
解决快速点击问题
当我们连续快速点击的时候,会启动很多个协程,这样会可能造成,后面启动的协程执行结果比前面启动的快。这样的情况,我们可以通过禁用按钮的形式来解决。当协程还没有返回结果的时候,可以禁用按钮。除此之外,还可以通过协程自身的api解决。
并发模式
解决以上的问题,我们可以通过控制协程运行和不运行来解决
(1)启动更多协程之前取消之前的任务
(2)让下一个协程任务排队,等待上一个执行完成
(3)如果已经有一个任务正在执行,返回该任务,而不是继续执行
方案 1: 取消之前的任务
val controllerRuuner = ControlledRunner<List<User>>()
// 方案 1: 取消之前的任务
// 对于排序和过滤的情况,新请求进来,取消上一个,这样的方案是很适合的。
suspend fun queryUser(): List<User> {
// 在开启新的排序之前,先取消上一个排序任务
return controllerRuuner.cancelPreviousThenRun {
userDao.queryUser()
}
}
方案 2: 让下一个任务排队等待
val singleRunner = SingleRunner()
suspend fun queryUser(): List<User> {
// 开始新的任务之前,等待之前的排序任务完成
return singleRunner.afterPrevious {
userDao.queryUser()
}
}
方案 3: 复用前一个任务
var controlledRunner = ControlledRunner<List<User>>()
suspend fun queryUser(): List<User> {
// 如果已经有一个正在运行的请求,那么就返回它。如果没有的话,开启一个新的请求。
return controlledRunner.joinPreviousOrRun {
userDao.queryUser()
}
}