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()}
}
}
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(); };
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 principaleUI
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 CommonPool
bgContext
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
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.
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"
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
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
}
Toutes les réponses ci-dessus sont à droite, mais j'ai été un moment difficile de trouver le droit à l'importation pour l' UI
kotlinx.coroutines
, il était en conflit avec UI
Anko
.
Ses
import kotlinx.coroutines.experimental.android.UI