Kotlin coroutine de la bonne façon dans Android

j'essaye de mettre à jour une liste à l'intérieur de l'adaptateur en utilisant async, je peux voir qu'il y a trop de boilerplate.

Est-ce la bonne façon d'utiliser les Coroutines Kotlin?

peut-on l'optimiser davantage?

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()}
        }
    }
25
demandé sur KTCO 2017-03-31 06:46:59

7 réponses

après avoir lutté avec cette question pendant des jours, je pense que le modèle le plus simple et clair async-attente pour les activités Android en utilisant Kotlin est:

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.
    }
}

La seule Gradle dépendances pour les coroutines sont: kotlin-stdlib-jre7,kotlinx-coroutines-android.

Remarque: Utiliser job.await() au lieu de job.join() parce que await() renvoie exceptions, mais join() n'est pas. Si vous utilisez join() vous aurez besoin de vérifier job.isCompletedExceptionally une fois le travail terminé.

Pour commencer simultanées rénovation des appels, vous pouvez faire ceci:

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

Ou:

val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
29
répondu KTCO 2017-04-19 02:18:16

Comment lancer une coroutine

Dans le kotlinx.coroutines bibliothèque vous pouvez lancer la nouvelle coroutine en utilisant l'un ou l'autre launch ou async fonction.

sur le plan Conceptuel, async est comme launch. Il commence une coroutine séparée qui est un fil léger qui fonctionne concurremment avec tous les autres coroutines.

la différence est que launch renvoie un Job et ne porte aucune valeur résultante, alors que async retourne un Deferred - un avenir non-bloquant de poids léger qui représente une promesse de fournir un résultat plus tard. Vous pouvez utiliser .await() reporter la valeur pour obtenir son résultat final, mais Deferred est aussi un Job, de sorte que vous pouvez l'annuler si nécessaire.

contexte Coroutine

dans Android nous utilisons habituellement deux contextes:

  • uiContext pour l'expédition d'exécution sur l'Android principale UI thread (pour le parent coroutine).
  • bgContext pour l'expédition d'exécution dans le thread d'arrière-plan (pour l'enfant coroutines).

Exemple

//dispatches execution onto the Android main UI thread
private val uiContext: CoroutineContext = UI

//represents a common pool of shared threads as the coroutine dispatcher
private val bgContext: CoroutineContext = CommonPool

Dans l'exemple suivant, nous allons utiliser CommonPoolbgContext qui limitent le nombre de threads courant en parallèle à la valeur de Runtime.getRuntime.availableProcessors()-1. Donc si la tâche coroutine est programmée, mais que tous les noyaux sont occupés, elle sera mise en file d'attente.

Vous pouvez envisager d'utiliser newFixedThreadPoolContext ou votre propre implémentation de cache thread pool.

lancement + async (exécution de la tâche)

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
}

lancement + async + async (exécuter deux tâches de manière séquentielle)

Note: les tâches 1 et 2 sont exécutées l'une après l'autre.

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

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

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

    val result = "$result1 $result2" // ui thread

    view.showData(result) // ui thread
}

lancement + async + async (exécuter deux tâches en parallèle)

Note: les tâches 1 et 2 sont exécutées en parallèle.

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

    val task1 = async(bgContext) { dataProvider.loadData("Task 1") }
    val task2 = async(bgContext) { dataProvider.loadData("Task 2") }

    val result = "${task1.await()} ${task2.await()}" // non ui thread, suspend until finished

    view.showData(result) // ui thread
}

Comment annulez une coroutine!--30-->

La fonction loadData retourne un Job objet qui peut être annulée. Lorsque la coroutine du parent est annulée, tous ses enfants sont annulés de façon récursive, aussi.

Si le stopPresenting la fonction a été appelée lors de l' dataProvider.loadData était encore en cours, la fonction view.showData ne sera jamais appelé.

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
}

La réponse complète est disponible dans mon article Androïde Coroutine Recettes

15
répondu Dmytro Danylyk 2017-12-20 11:11:18

je pense que vous pouvez vous débarrasser de runOnUiThread { ... } en utilisant UI contexte pour les applications Android au lieu de CommonPool.

UI le contexte est fourni par l' kotlinx-coroutines-android module.

7
répondu Steffen 2017-03-31 08:54:15

Nous avons aussi une autre option. si nous utilisons Anko bibliothèque , puis de elle ressemble à ceci

doAsync { 

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

ajouter une dépendance pour Anko dans votre app gradle comme ceci.

compile "org.jetbrains.anko:anko:0.10.3"
5
répondu Suraj Nair 2017-11-29 14:46:56

comme sdeff l'a dit, si vous utilisez le contexte UI, le code à l'intérieur de cette coroutine s'exécutera sur le thread UI par défaut. Et, si vous avez besoin d'exécuter une instruction sur un autre fil, vous pouvez utiliser run(CommonPool) {}

en Outre, si vous n'avez pas besoin renvoyer rien de la méthode, vous pouvez utiliser la fonction launch(UI) au lieu de async(UI) (le premier sera de retour Job et le dernier un Deferred<Unit>).

Un exemple pourrait être:

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()
    }
}

si vous avez besoin de plus d'aide je vous recommande pour lire le guide principal de kotlinx.coroutines et, en outre, l' guide des coroutines + UI

3
répondu David Olmos 2017-12-28 21:14:44

si vous voulez retourner quelque chose du thread d'arrière-plan utilisez async

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

Si le thread d'arrière-plan n'est pas de retour rien

launch(UI) {
   launch(CommonPool) {
      //do long running operation   
   }.await()
   //do stuff on UI thread
}
1
répondu Rocky 2018-02-14 09:38:08

Toutes les réponses ci-dessus sont à droite, mais j'ai été un moment difficile de trouver le droit à l'importation pour l' UIkotlinx.coroutines, il était en conflit avec UIAnko. Ses

import kotlinx.coroutines.experimental.android.UI
1
répondu Max 2018-04-02 09:47:50