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

问题描述 投票:2回答:5

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

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

有人问过,但是我无法让它发挥作用。我已经阅读了类似问题的答案,但有些已被弃用,或者某些解决方案似乎很复杂,应该是微不足道的。

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

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

这是我想在后台线程中进行查询的函数之一:

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

我可以调用函数如下,它可以工作,但这意味着我需要在其他类中添加异步调用,它看起来不优雅:

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 coroutines,Live Data和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()
    }
}
android kotlin rx-java android-room kotlin-coroutines
5个回答
7
投票

Step One

转动你的阻止功能

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

进入暂停状态:

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

Step Two

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

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

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


1
投票

更新:我的回答是错误的。 (感谢Marko)它确实启动了另一个后台线程,但它仍然阻止了UI线程。因此,这绕过了房间保护,不在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>
}

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

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

它有很多简单代码的例子,有助于掌握细节并查看/尝试它们。


0
投票

使用Room默认情况下,所有对数据库的请求都必须在后台线程中进行。为什么不使用协同程序?你可以用这样的东西:

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

0
投票

我对AsyncTask并不是很熟悉,但看起来你甚至在后台任务实际完成获取数据并为其分配值之前就返回变量'crime'。

在AsyncTask完成获取数据后,您需要返回犯罪(数据库查询的结果)。您甚至在后台完成之前就已经返回了犯罪,因为您在execute()调用之后立即放置了return语句。

如果要使用AsyncTask,则需要实现回调(即onPostExecute(),doInBackground()等)。您可以从doInBackground()回调中返回数据库中的结果数据(在本例中为犯罪)。

我个人更喜欢将RxJava与Room数据库而不是AsyncTask一起使用,因为它使您的代码更清晰,更直接:)


0
投票

我所知道的最好的方法是使用协同程序。哟可以使用这样的东西:

//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>)
    }
    ....
}
© www.soinside.com 2019 - 2024. All rights reserved.