Kotlin Coroutines właściwy sposób w Androidzie
Próbuję zaktualizować listę wewnątrz adaptera za pomocą asynchronizacji, widzę, że jest za dużo boilerplate.
Czy to dobry sposób na używanie Koroutinów Kotlin?
Czy można to bardziej zoptymalizować?
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 answers
Po kilku dniach zmagania się z tym pytaniem, myślę, że najprostszym i najbardziej przejrzystym wzorcem asynchronicznym dla działań z Androidem przy użyciu Kotlina jest:
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.
}
}
Jedynymi zależnościami Gradle dla coroutines są: kotlin-stdlib-jre7
, kotlinx-coroutines-android
.
Uwaga: Użyj job.await()
zamiast job.join()
, ponieważ await()
zmienia wyjątki, ale join()
nie. Jeśli używasz join()
, musisz sprawdzić job.isCompletedExceptionally
po zakończeniu zadania.
Aby rozpocząć współbieżne , możesz wykonać to:
val jobA = async(CommonPool) { /* Blocking call A */ };
val jobB = async(CommonPool) { /* Blocking call B */ };
jobA.await();
jobB.await();
Lub:
val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-19 02:18:16
Jak uruchomić koroutine
W bibliotece kotlinx.coroutines
możesz uruchomić nowy coroutine używając funkcji launch
lub async
.
Koncepcyjnie, async
jest jak launch
. Rozpoczyna oddzielną koronę, która jest lekką nicią, która działa jednocześnie ze wszystkimi innymi koronami.
Różnica polega na tym, że launch zwraca Job
i nie niesie żadnej wartości wynikowej, podczas gdy async
zwraca Deferred
- lekką, nieblokującą przyszłość, która reprezentuje obietnica dostarczenia rezultatu później. Możesz użyć .await()
na wartości odroczonej, aby uzyskać jej ostateczny wynik, ale Deferred
jest również Job
, więc możesz ją anulować w razie potrzeby.
Coroutine context
W Androidzie zwykle używamy dwóch kontekstów:]}-
uiContext
Aby wysłać wykonanie do głównego wątku AndroidaUI
(dla rodzica) . -
bgContext
Aby wysłać wykonanie w wątku tła (dla dziecka koroutines) .
Przykład
//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
W poniższym przykładzie użyjemy CommonPool
dla bgContext
, które ograniczają liczbę wątków działających równolegle do wartości Runtime.getRuntime.availableProcessors()-1
. Więc jeśli zadanie coroutine jest zaplanowane, ale wszystkie rdzenie są zajęte, zostanie ono ustawione w kolejce.
Możesz rozważyć użycie newFixedThreadPoolContext
lub własnej implementacji buforowanej puli wątków.
Uruchom + async (execute task)
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
}
Launch + async + async (wykonaj dwa zadania kolejno)
Uwaga: task1 i task2 są wykonywane kolejno.
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
}
Uruchom + async + async (wykonaj dwa zadania równolegle)
Uwaga: task1 i task2 są wykonywane równolegle.
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
}
Jak anulować koroutine
Funkcja loadData
zwraca obiekt Job
, który może zostać anulowany. Gdy koroutine rodzica jest anulowany, wszystkie jego dzieci są również rekurencyjnie anulowane.
Jeśli stopPresenting
funkcja została wywołana gdy dataProvider.loadData
była jeszcze w toku, funkcja view.showData
nigdy nie zostanie wywołana.
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
}
Pełna odpowiedź jest dostępna w moim artykule Android Coroutine Recipes
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-20 11:11:18
Myślę, że możesz pozbyć się runOnUiThread { ... }
używając kontekstu UI
dla aplikacji na Androida zamiast CommonPool
.
Kontekst UI
jest dostarczany przez moduł kotlinx-coroutines-android.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-31 08:54:15
Mamy też inną opcję. jeśli używamy biblioteki Anko , wygląda to tak
doAsync {
// Call all operation related to network or other ui blocking operations here.
uiThread {
// perform all ui related operation here
}
}
Dodaj zależność dla Anko w swojej aplikacji gradle w ten sposób.
compile "org.jetbrains.anko:anko:0.10.3"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-29 14:46:56
Jak powiedział sdeff, jeśli użyjesz kontekstu UI, kod wewnątrz którego coroutine będzie domyślnie uruchamiany w wątku UI. Jeśli chcesz uruchomić instrukcję w innym wątku, możesz użyć run(CommonPool) {}
Ponadto, jeśli nie musisz zwracać nic z metody, możesz użyć funkcji launch(UI)
zamiast async(UI)
(Pierwsza zwróci a Job
, a druga a Deferred<Unit>
).
Przykładem może być:
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()
}
}
Jeśli potrzebujesz więcej pomocy, polecam przeczytać główny przewodnik kotlinx.coroutines oraz dodatkowo przewodnik po coroutines + UI
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-28 21:14:44
Jeśli chcesz zwrócić coś z wątku tła użyj asynchronizacji
launch(UI) {
val result = async(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
view.setText(result)
}
Jeśli wątek tła nie zwraca niczego
launch(UI) {
launch(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-14 09:38:08
Wszystkie powyższe odpowiedzi są słuszne, ale ciężko mi było znaleźć odpowiedni import dla UI
z kotlinx.coroutines
, był sprzeczny z UI
z Anko
.
Its
import kotlinx.coroutines.experimental.android.UI
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-02 09:47:50