Android Room Kotlin-在后台线程中查询-返回值问题

时间:2018-10-10 18:45:22

标签: android kotlin rx-java android-room kotlin-coroutines

我已将示例程序从Java / SQLite转换为Kotlin / Room。

我正在努力用后台线程中的返回值来实现查询。

有人问过这个问题,但我无法正常工作。 我已经阅读了类似问题的答案,但有些问题已被弃用,或者某些问题的解决方案似乎很复杂。

当我需要使用查询的返回值时,我真的很想出一个简单的解决方案。

(如果我使用allowMainThreadQueries()强制在主线程中执行查询,那么一切都会正常工作

这是我想在后台线程中执行查询的功能之一:

fun getCrimes(): List<Crime> {
    val crimes = crimesDAO.getAllCrimes() as ArrayList<Crime>
    return crimes
}

我可以像下面那样调用该函数,并且该函数可以正常工作,但这意味着我需要在其他所有类中添加Async调用,而且看起来并不优雅:

AsyncTask.execute {
    mCrimes = getCrimes() as ArrayList<Crime>
}

==>我想修改getCrimes本身以使其在后台运行查询,例如:(后接错误代码)

fun getCrimes(): List<Crime> {
    var crimes: ArrayList<Crime>

    AsyncTask.execute {
        crimes = crimesDAO.getAllCrimes() as ArrayList<Crime>
    }

    return crimes // This is wrong as crimes in not initialized
}

我研究了kotlin协程,实时数据和rxjava,但是找不到解决此问题的简单方法。

背景信息:

这是数据类:

@Entity(tableName = "crimes_table")
class Crime {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name="id")
    var id: Long = 0

    @ColumnInfo(name="uuid")
    @TypeConverters(UUIDConverter::class)
    var mId: UUID = UUID.randomUUID()

    @ColumnInfo(name="title")
    var mTitle: String? = null

    @ColumnInfo(name="date")
    @TypeConverters(DateConverter::class)
    var mDate: Date? = Date()

    @ColumnInfo(name="solved")
    var mSolved: Boolean = false
}

这是DAO:

@Dao
interface CrimesListDAO {

    @Query("SELECT * FROM crimes_table")
    fun getAllCrimes(): List<Crime>

    @Query("SELECT * FROM crimes_table WHERE uuid = :uuidString LIMIT 1")
    fun getOneCrime(uuidString: String): Crime

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertCrime(crime: Crime)

    @Update(onConflict = OnConflictStrategy.REPLACE)
    fun updateCrime(crime: Crime)

    @Delete
    fun deleteCrime(crime: Crime)
}

这是DatabaseApp类:

@Database(entities = [(Crime::class)], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun crimesListDAO(): CrimesListDAO
}

这是我实例化数据库的方式:

class ApplicationContextProvider : Application() {
    ...
    companion object {
        var database: AppDatabase? = null
    ...
    }

    override fun onCreate() {
        super.onCreate()
        ApplicationContextProvider.database = Room.databaseBuilder(this, AppDatabase::class.java, "crimeBase.db").build()
    }
}

5 个答案:

答案 0 :(得分:6)

第一步

打开您的阻止功能

fun getCrimes() = crimesDAO.getAllCrimes() as List<Crime>

变成悬浮的:

suspend fun getCrimes() = withContext(Dispatchers.IO) { 
    crimesDAO.getAllCrimes() as List<Crime>
}

第二步

要调用可挂起的函数,必须首先启动协程:

override fun onSomeEvent() {
    (context as CoroutineScope).launch {
        val crimes = getCrimes()
        // work with crimes
    }
}

要了解如何使您的context成为CoroutineScope,请参阅documentation on CoroutineScope

答案 1 :(得分:0)

使用Room默认情况下,所有对数据库的请求都必须在后台线程中进行。为什么不使用协程? 您可以将其与以下内容一起使用:

suspend fun retrieveCrimes(): List<Crime> {
    return async {
        delay(5000)
        1
    }.await()
}

答案 2 :(得分:0)

我对AsyncTask并不是很熟悉,但是似乎您在后台任务实际完成数据获取并为其分配值之前就返回了变量“ crime”。

在AsyncTask完成获取数据之后,您需要返回犯罪(数据库查询的结果)。即使在后台完成之前,您也要归还犯罪,因为您将return语句放在execute()调用之后。

如果要使用AsyncTask,则需要实现回调(即onPostExecute(),doInBackground()等)。您可以通过doInBackground()回调从数据库返回结果数据(在这种情况下为犯罪)。

我个人更喜欢将RxJava与Room数据库一起使用,而不是AsyncTask,因为它使您的代码更简洁明了:)

答案 3 :(得分:0)

我知道最好的方法是使用协程。 can可以使用类似这样的东西:

//For example fron the activity's onCreate method:
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    launch(UI){
        ....
        val allCrimes = bg{
            crimesDAO.getAllCrimes()
        }.await()
        //do something with allCrimes (as a List<Crime>)
    }
    ....
}

答案 4 :(得分:0)

更新:我的回答是错误的。 (感谢Marko) 它确实启动了另一个后台线程,但是仍然阻止了UI线程。 因此,这绕开了Room保护机制,使其无法在UI线程中进行调用,但却无法达到目的。

我使用下面的代码来确认我正在生成一个新线程,但无论如何都会阻止调用者线程:

fun main(args: Array<String>) {
    exampleBlockingDispatcher()
}

suspend fun printlnDelayed(message: String) {
    delay(2000)
    println(message)
}

// Running on another thread but still blocking the main thread
fun exampleBlockingDispatcher(){
    runBlocking(Dispatchers.Default) {
        println("one - from thread ${Thread.currentThread().name}")
        printlnDelayed("two - from thread ${Thread.currentThread().name}")
    }
    // Outside of runBlocking to show that it's running in the blocked main thread
    println("three - from thread ${Thread.currentThread().name}")
    // It still runs only after the runBlocking is fully executed.
}

原始答案:

几个小时后,我发现了(编辑:我希望)。在后台线程中使用协程调用DAO方法并能够返回值的正确方法是:

fun getCrimes(): ArrayList<Crime> = runBlocking(Dispatchers.Default) {
    val result = async { crimesDAO.getAllCrimes() }.await()
    return@runBlocking result as ArrayList<Crime>
}

阅读了很多代码和教程,但是到目前为止,我推荐的协程中最好的是这个:

https://resocoder.com/2018/10/06/kotlin-coroutines-tutorial-stable-version-async-await-withcontext-launch/

它有很多带有简单代码的示例,可帮助您掌握细节并实际观察/尝试。