48 votes

Kotlin Coroutines dans Android

J'essaie de mettre à jour une liste à l'intérieur de l'adaptateur en utilisant async, je peux voir qu'il y a trop de passe-partout.

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

cela peut-il être optimisé 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()}
        }
    }
 

44voto

KTCO Points 1123

Après avoir lutté avec cette question de jours, je pense que le plus simple et clair asynchrone attendent modèle pour Android activités à l'aide de 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() car await() renvoie exceptions, mais join() ne le sont pas. Si vous utilisez join() vous aurez besoin de vérifier job.isCompletedExceptionally une fois le travail terminé.

Pour commencer simultanées de 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(); };

40voto

Dmytro Danylyk Points 6911

Comment lancer une coroutine

Dans l' kotlinx.coroutines bibliothèque, vous pouvez commencer à nouveau coroutine en utilisant soit launch ou async fonction.

Sur le plan conceptuel, async est juste comme launch. Il démarre une nouvelle coroutine qui est un poids léger fil qui fonctionne simultanément avec toutes les autres coroutines.

La différence est que le lancement renvoie une Job et n'a aucune valeur, alors que async renvoie un Deferred - un peu de poids non-blocage de l'avenir que représente une promesse de fournir un résultat ultérieurement. Vous pouvez utiliser .await() sur un différés à la valeur d'obtenir son résultat final, mais Deferred est aussi un Job, de sorte que vous pouvez l'annuler si nécessaire.

Coroutine contexte

Dans Android, nous avons l'habitude d'utiliser deux contexte:

  • uiContext à l'expédition d'exécution sur l'Android principale UI fil (pour le parent coroutine).
  • bgContext à 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 CommonPool pour bgContext qui limite le nombre de threads en parallèle, la valeur de Runtime.getRuntime.availableProcessors()-1. Donc, si la coroutine tâche est planifiée, mais tous les cœurs sont occupés, il sera mis en file d'attente.

Vous pouvez envisager d'utiliser newFixedThreadPoolContext ou votre propre mise en œuvre de la mise en cache du pool de threads.

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)

Remarque: task1 et task2 sont exécutées de manière séquentielle.

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)

Remarque: task1 et task2 sont exécutés 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 faire pour annuler une coroutine

La fonction loadData renvoie un Job objet qui peut être annulée. Lorsque le parent coroutine est annulé, tous ses enfants sont récursivement annulé, trop.

Si l' stopPresenting 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 Android Coroutine Recettes

9voto

Steffen Points 1197

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

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

6voto

Suraj Nair Points 854

Nous avons aussi une autre option. si nous utilisons la bibliothèque Anko , alors cela 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 application gradé comme ceci.

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

3voto

David Olmos Points 31

Comme sdeff dit, si vous utilisez l'INTERFACE utilisateur contexte, le code de l'intérieur que coroutine sera exécuté sur le thread de l'INTERFACE utilisateur 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) (l'ancien sera de retour un Job et le second une 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 de lire le guide principal de kotlinx.coroutines et, en outre, le guide de coroutines + UI

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X