Kotlin-Coroutines

Android开发中使用协程(代码实战)

2020-11-23  本文已影响0人  大虾啊啊啊

使用协程完成本地数据库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()
        }
    }

源码

https://gitee.com/daxiaa/coroutine-scope-room.git

上一篇下一篇

猜你喜欢

热点阅读