Котлин Coroutines правильный путь в Android

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

правильно ли использовать Kotlin Coroutines?

можно ли оптимизировать это больше?

fun loadListOfMediaInAsync() = async(CommonPool) {
        try {
            //Long running task 
            adapter.listOfMediaItems.addAll(resources.getAllTracks())
            runOnUiThread {
                adapter.notifyDataSetChanged()
                progress.dismiss()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            runOnUiThread {progress.dismiss()}
        } catch (o: OutOfMemoryError) {
            o.printStackTrace()
            runOnUiThread {progress.dismiss()}
        }
    }

7 ответов


после борьбы с этим вопросом в течение нескольких дней, я думаю, что самый простой и понятный шаблон асинхронного ожидания для Android-действий с помощью Kotlin:

override fun onCreate(savedInstanceState: Bundle?) {
    //...
    loadDataAsync(); //"Fire-and-forget"
}

fun loadDataAsync() = async(UI) {
    try {
        //Turn on busy indicator.
        val job = async(CommonPool) {
           //We're on a background thread here.
           //Execute blocking calls, such as retrofit call.execute().body() + caching.
        }
        job.await();
        //We're back on the main thread here.
        //Update UI controls such as RecyclerView adapter data.
    } 
    catch (e: Exception) {
    }
    finally {
        //Turn off busy indicator.
    }
}

единственный зависимостей Gradle в для сопрограммы являются: kotlin-stdlib-jre7, kotlinx-coroutines-android.

Примечание: использовать job.await() вместо job.join(), потому что await() возвращает исключения, но join() нет. Если вы используете join() вам нужно будет проверить job.isCompletedExceptionally после завершения задания.

начать одновременно retrofit звонки, вы можете сделать это:

val jobA = async(CommonPool) { /* Blocking call A */ };
val jobB = async(CommonPool) { /* Blocking call B */ };
jobA.await();
jobB.await();

или:

val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };

как запустить корутину

на kotlinx.coroutines библиотека вы можете запустить новый coroutine, используя либо launch или был призван dataProvider.loadData все еще продолжалось, функция view.showData никогда не будет вызван.

var job: Job? = null

fun startPresenting() {
    job = loadData()
}

fun stopPresenting() {
    job?.cancel()
}

private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread

    val task = async(bgContext) { dataProvider.loadData("Task") }
    val result = task.await() // non ui thread, suspend until finished

    view.showData(result) // ui thread
}

полный ответ доступен в моей статье Android Coroutine Рецепты


Я думаю, вы можете избавиться от runOnUiThread { ... } с помощью UI контекст для приложений Android вместо CommonPool.

на UI контекст, предусмотренных kotlinx-coroutines-android.


у нас также есть другой вариант. если мы используем Анко библиотека, тогда это выглядит так

doAsync { 

    // Call all operation  related to network or other ui blocking operations here.
    uiThread { 
        // perform all ui related operation here    
    }
}

добавить зависимость для Anko в вашем приложении gradle, как это.

compile "org.jetbrains.anko:anko:0.10.3"

как сказал sdeff, если вы используете контекст пользовательского интерфейса, код внутри этой подпрограммы будет работать в потоке пользовательского интерфейса по умолчанию. И, если вам нужно запустить инструкцию в другом потоке, вы можете использовать run(CommonPool) {}

кроме того, если вам не надо ничего возвращать из метода, вы можете использовать функцию launch(UI) вместо async(UI) (бывшая вернет Job и последний Deferred<Unit>).

например:

fun loadListOfMediaInAsync() = launch(UI) {
    try {
        withContext(CommonPool) { //The coroutine is suspended until run() ends
            adapter.listOfMediaItems.addAll(resources.getAllTracks()) 
        }
        adapter.notifyDataSetChanged()
    } catch(e: Exception) {
        e.printStackTrace()
    } catch(o: OutOfMemoryError) {
        o.printStackTrace()
    } finally {
        progress.dismiss()
    }
}

Если вам нужна дополнительная помощь, я рекомендую вам читать главное руководство kotlinx.coroutines и, кроме того, руководство coroutines + UI


Если вы хотите вернуть что-то из фонового потока, используйте async

launch(UI) {
   val result = async(CommonPool) {
      //do long running operation   
   }.await()
   //do stuff on UI thread
   view.setText(result)
}

Если фоновый поток, ничего не возвращается

launch(UI) {
   launch(CommonPool) {
      //do long running operation   
   }.await()
   //do stuff on UI thread
}

все вышеперечисленные ответы верны, но мне было трудно найти правильный импорт для UI С kotlinx.coroutines, это противоречило UI С Anko. Его

import kotlinx.coroutines.experimental.android.UI