Android Room Kotlin - Запрос в фоновом потоке - проблема с возвращаемым значением - PullRequest
0 голосов
/ 10 октября 2018

Я преобразовал пример программы из Java / SQLite в Kotlin / Room.

Я пытаюсь реализовать запросы с возвращаемыми значениями в фоновом потоке.

Это было задано, но яне мог заставить его работать.Я прочитал ответы на подобные вопросы, но некоторые устарели, или некоторые решения кажутся сложными для чего-то, что должно быть тривиальным.

Я действительно озадачен, придя к простому решению, когда мне нужно использовать возвращаемое значение запроса.

(Все работает как надо, если я заставляю делать запросы в главном потокеwith 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, 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()
    }
}

Ответы [ 5 ]

0 голосов
/ 11 октября 2018

Шаг первый

Превратите функцию блокировки

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, обратитесь к документации по CoroutineScope.

0 голосов
/ 11 октября 2018

ОБНОВЛЕНИЕ: мой ответ неверный.(спасибо Марко) Он запускает другой фоновый поток, НО все еще блокирует поток пользовательского интерфейса.Таким образом, это позволяет обойти защиту Room, чтобы она не выполняла вызовы в потоке пользовательского интерфейса, но это отрицательно сказывается на цели.

Я использовал приведенный ниже код, чтобы подтвердить, что я генерирую новый поток, но в любом случае блокирую поток вызывающего:

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/

В нем много примеров с простым кодом, который помогает понять детали и увидеть / попробовать их в действии.

0 голосов
/ 10 октября 2018

Я не очень знаком с AsyncTask, но кажется, что вы возвращаете переменную 'crime' еще до того, как фоновая задача фактически закончила извлечение данных и присвоила им значение.

Вам необходимо вернуть преступления (результат запроса к БД) после того, как AsyncTask завершил выборку данных.Вы возвращаете преступления еще до того, как фон завершен, потому что вы помещаете инструкцию return сразу после вызова execute ().

Если вы хотите использовать AsyncTask, вам необходимо реализовать обратные вызовы (например, onPostExecute (), doInBackground () и т. Д.).Вы можете вернуть данные результатов из БД (в данном случае преступления) из обратного вызова doInBackground ().

Я лично предпочитаю использовать RxJava с базой данных Room, а не AsyncTask, потому что это делает ваш код чище и понятнее :)

0 голосов
/ 11 октября 2018

Лучший из известных мне подходов - использовать сопрограммы.Йо может использовать что-то вроде этого:

//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>)
    }
    ....
}
0 голосов
/ 10 октября 2018

Используя Room, все запросы к базе данных по умолчанию должны выполняться в фоновом потоке.Почему бы не использовать сопрограммы?Вы можете использовать его с чем-то вроде этого:

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