Współrzędne partnerskie Kotlin pozwalają napisać czysty, uproszczony kod asynchroniczny, który zachowuje responsywność aplikacji przy zarządzaniu długotrwałymi zadaniami, takimi jak wywołania sieciowe. lub operacje na dysku.
W tym artykule znajdziesz szczegółowe omówienie współrzędnych na Androidzie. Jeśli nie znasz współrzędnych, koniecznie przeczytaj Zanim przeczytasz ten temat, Kotlin współpracuje na Androidzie.
Zarządzaj długotrwałymi zadaniami
Kogutyny bazują na zwykłych funkcjach przez dodanie 2 operacji do obsługi
do długotrwałych zadań. Oprócz invoke
(lub call
) i return
,
współprogramy dodają suspend
i resume
:
suspend
wstrzymuje wykonanie bieżącej współrzędnej, zapisując wszystkie zmiennych.resume
kontynuuje wykonywanie zawieszonej współprogramu w miejscu na którym zostały zawieszone.
Funkcje suspend
możesz wywoływać tylko z poziomu innych funkcji suspend
lub
za pomocą kreatora współoutin, takiego jak launch
, aby uruchomić nową współrzędną.
Poniższy przykład pokazuje prostą implementację współrzędną dla hipotetyczne zadanie długotrwałe:
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("https://developer.android.com") // Dispatchers.IO for `get`
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }
W tym przykładzie reguła get()
nadal działa w wątku głównym, ale zawiesza
przed rozpoczęciem żądania sieciowego. Gdy żądanie sieciowe
zakończy, get
wznawia zawieszoną współpracę zamiast używać wywołania zwrotnego
, aby powiadomić wątek główny.
Kotlin wykorzystuje ramkę stosu do określania, która funkcja działa z dowolnymi zmiennymi lokalnymi. Przy zawieszaniu współprogramu bieżący stos jest skopiowana i zapisana na później. Podczas wznawiania ramka stosu ma postać zostanie skopiowany z powrotem z miejsca, w którym została zapisana, a funkcja zostanie ponownie uruchomiona. Mimo że kod może wyglądać jak zwykłe, sekwencyjne blokowanie żądania sieciowe, zapewnia, że żądanie sieciowe nie będzie blokować w wątku głównym.
Używaj wspólnych reguł na potrzeby głównego bezpieczeństwa
W Kotlinach używane są dyspozytory określające, które wątki są używane wspólnego wykonania. Aby uruchomić kod poza wątkiem głównym, możesz poinformować Kotlin i wykonują pracę nad dyspozytorem domyślnym lub IO. W Kotlin, wszystkie współudziały muszą być uruchomione w dyspozytorze, nawet jeśli są uruchamiane w wątku głównym. Koutyny mogą zawiesić się, a dyspozytor i odpowiada za ich wznowienie.
Aby określić, gdzie powinny być uruchamiane współudziały, Kotlin udostępnia 3 dyspozytorów których możesz użyć:
- Dispatchers.Main – użyj tego dyspozytora, by uruchomić współprogram w głównym
Wątek dotyczący Androida. Należy go używać wyłącznie do interakcji z interfejsem użytkownika
szybkiej pracy. Przykłady obejmują wywoływanie funkcji
suspend
, uruchamianie operacje i aktualizacje platformy interfejsu Androida;LiveData
obiektów. - Dispatchers.IO – ten dyspozytor jest zoptymalizowany do wykonywania zadań na dyskach lub w sieci Wejście-wyjście poza wątkiem głównym. Przykłady: komponent Sala, do odczytywania plików i zapisywania do nich oraz wykonywania operacji sieciowych.
- Dispatchers.Default – ten dyspozytor jest zoptymalizowany pod kątem Obciążenie procesora przez pracę poza wątkiem głównym. Przykładowe zastosowania to sortowanie i przeanalizować kod JSON.
Kontynuując poprzedni przykład, możesz użyć dyspozytorów, aby ponownie zdefiniować
get
. Wewnątrz treści get
wywołaj withContext(Dispatchers.IO)
do
utworzyć blok, który działa w puli wątków we/wy. każdy umieszczony w nim kod,
jest zawsze wykonywany przez dyspozytora IO
. Ponieważ withContext
jest sam w sobie
funkcji zawieszenia (get
) jest również funkcją zawieszania.
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("developer.android.com") // Dispatchers.Main
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = // Dispatchers.Main
withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block)
/* perform network IO here */ // Dispatchers.IO (main-safety block)
} // Dispatchers.Main
}
Dzięki współprogramom możesz wysyłać wątki ze szczegółową kontrolą. Ponieważ
withContext()
pozwala kontrolować pulę wątków dowolnego wiersza kodu bez konieczności
wprowadzając wywołania zwrotne, można je zastosować do bardzo małych funkcji, takich jak odczytywanie
z bazy danych
lub wykonując żądania sieciowe. Zalecaną praktyką jest
withContext()
, aby zapewnić, że każda funkcja jest bezpieczna, co oznacza
może wywołać funkcję z wątku głównego. Dzięki temu rozmówca nigdy nie będzie musiał
i pomyśl, którego wątku
należy użyć do wykonania funkcji.
W poprzednim przykładzie polecenie fetchDocs()
jest wykonywane w wątku głównym; jednak
może bezpiecznie wywołać get
, który wykonuje żądanie sieciowe w tle.
Ponieważ współprogramy obsługują suspend
i resume
, współrzędna w głównym
wątek zostanie wznowiony z wynikiem get
, gdy tylko blok withContext
zostanie
gotowe.
Wydajność withContext()
withContext()
nie stanowi dodatkowego narzutu w porównaniu z równoważnym rozwiązaniem opartym na wywołaniu zwrotnym
implementacji. Ponadto można zoptymalizować wywołania funkcji withContext()
w pewnych sytuacjach wykraczające poza równoważną implementację opartą na wywołaniach zwrotnych. Dla:
na przykład jeśli funkcja wysyła dziesięć wywołań sieci, możesz użyć polecenia Kotlin
przełączać wątki tylko raz przy użyciu zewnętrznego elementu withContext()
. Potem, mimo że
biblioteka sieciowa używa funkcji withContext()
wielokrotnie, pozostaje bez zmian
z dyspozytorem i unikać przełączania wątków. Kotlin optymalizuje też przechodzenie
między Dispatchers.Default
a Dispatchers.IO
, by uniknąć przełączania wątków
gdy tylko jest to możliwe.
Rozpoczynanie współpracy
Współrzędne można rozpoczynać na 2 sposoby:
launch
rozpoczyna nową współudział i nie zwraca wyniku wywołującemu. Dowolne utwór uważany za „ogień i zapomnienie” można rozpocząć za pomocą funkcjilaunch
.async
uruchamia nową współpracę i umożliwia zwrócenie wyniku ze stanem zawieszenia funkcja o nazwieawait
.
Zwykle należy launch
nową współudział ze zwykłej funkcji,
jako zwykła funkcja nie może wywołać funkcji await
. Używaj async
tylko w środku
w innej koryturze lub w ramach funkcji zawieszenia
rozkładu równoległego.
Rozkład równoległy
Wszystkie współprogramy uruchomione w funkcji suspend
muszą zostać zatrzymane, gdy
ta funkcja się zwraca, więc prawdopodobnie musisz zagwarantować, że te współrzędne
zanim ją zwrócisz. Dzięki ustrukturyzowanej równoczesności w Kotlin możesz określić,
coroutineScope
, który uruchamia co najmniej 1 współpracę. Następnie za pomocą funkcji await()
(w przypadku pojedynczej korygu) lub awaitAll()
(w przypadku wielu współprogramów), możesz
zagwarantuje zakończenie tego procesu przed powrótem z funkcji.
Przykładowo zdefiniujmy coroutineScope
, który pobiera 2 dokumenty
asynchronicznie. Wywołując await()
przy każdym odroczonym pliku referencyjnym, gwarantujemy,
że obie operacje async
zakończą się przed zwróceniem wartości:
suspend fun fetchTwoDocs() =
coroutineScope {
val deferredOne = async { fetchDoc(1) }
val deferredTwo = async { fetchDoc(2) }
deferredOne.await()
deferredTwo.await()
}
Elementu awaitAll()
możesz też użyć w kolekcjach, jak w tym przykładzie:
suspend fun fetchTwoDocs() = // called on any Dispatcher (any thread, possibly Main)
coroutineScope {
val deferreds = listOf( // fetch two docs at the same time
async { fetchDoc(1) }, // async returns a result for the first doc
async { fetchDoc(2) } // async returns a result for the second doc
)
deferreds.awaitAll() // use awaitAll to wait for both network requests
}
Mimo że fetchTwoDocs()
uruchamia nowe współrzędne z async
, funkcja
używa funkcji awaitAll()
do oczekiwania na zakończenie uruchamiania współprac przez te, które zostały uruchomione przed
powrót do domu. Pamiętaj jednak, że nawet gdybyśmy nie wywołali awaitAll()
,
Kreator coroutineScope
nie wznawia współprogramu, który wywołał
fetchTwoDocs
do zakończenia wszystkich nowych współprogramów.
Dodatkowo coroutineScope
wykrywa wszystkie wyjątki zgłaszane przez współrzędne
i przekierowuje go z powrotem do rozmówcy.
Więcej informacji o rozkładzie równoległym znajdziesz w artykule Tworzenie funkcji zawieszania
Pojęcia związane z koutynami
CoroutineScope
CoroutineScope
śledzi wszystkie tworzone współprogramy za pomocą elementów launch
lub async
.
trwające prace (tj. aktywne współprace) można anulować, wywołując
scope.cancel()
. Na Androidzie niektóre biblioteki KTX udostępniają
własne CoroutineScope
dla określonych klas cyklu życia. Przykład:
ViewModel
ma
viewModelScope
,
a Lifecycle
ma lifecycleScope
.
Jednak w przeciwieństwie do dyspozytora CoroutineScope
nie uruchamia współrzędnych.
viewModelScope
jest też używany w przykładach w:
Wątki w tle na urządzeniach z Androidem za pomocą Coroutines.
Jeśli jednak musisz utworzyć własny element CoroutineScope
, aby kontrolować
cyklu życia współprogramów w określonej warstwie aplikacji, możesz utworzyć
w następujący sposób:
class ExampleClass {
// Job and Dispatcher are combined into a CoroutineContext which
// will be discussed shortly
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// Starts a new coroutine within the scope
scope.launch {
// New coroutine that can call suspend functions
fetchDocs()
}
}
fun cleanUp() {
// Cancel the scope to cancel ongoing coroutines work
scope.cancel()
}
}
Anulowany zakres nie może utworzyć więcej współrzędnych. Dlatego
Wywołuj funkcję scope.cancel()
tylko wtedy, gdy klasa kontroluje cykl życia
jest zniszczona. W przypadku użycia funkcji viewModelScope
makro
Zajęcia ViewModel
anulują
zakresu w metodzie onCleared()
modelu ViewModel.
Zadanie
Job
jest nickiem dla współprogramu. Każda współrzędna utworzona za pomocą funkcji launch
lub async
zwraca instancję Job
, która jednoznacznie identyfikuje
współdziała z internetem i zarządza jego cyklem życia. Możesz również przekazać Job
do
CoroutineScope
, aby dokładniej zarządzać cyklem życia, jak pokazano poniżej
przykład:
class ExampleClass {
...
fun exampleMethod() {
// Handle to the coroutine, you can control its lifecycle
val job = scope.launch {
// New coroutine
}
if (...) {
// Cancel the coroutine started above, this doesn't affect the scope
// this coroutine was launched in
job.cancel()
}
}
}
Kontekst Coroutine
CoroutineContext
definiuje zachowanie współrzędnej przy użyciu takiego zestawu elementów:
Job
: Kontroluje cykl życia współprogramu.CoroutineDispatcher
: Wysyłane są w odpowiednim wątku.CoroutineName
: Nazwa współrzędu, przydatna przy debugowaniu.CoroutineExceptionHandler
: Obsługuje niewykryte wyjątki.
W przypadku nowych współprogramów utworzonych w zakresie nowa instancja Job
jest
przypisane do nowej współrzędnej, a pozostałe elementy CoroutineContext
są dziedziczone z zakresu, który zawiera. Możesz zastąpić odziedziczone
elementów, przekazując nowy CoroutineContext
do launch
lub async
. Pamiętaj, że przekazanie wartości Job
do launch
lub async
nie ma żadnego efektu.
jako nowa instancja instancji Job
jest zawsze przypisywana do nowej kompatybilności.
class ExampleClass {
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// Starts a new coroutine on Dispatchers.Main as it's the scope's default
val job1 = scope.launch {
// New coroutine with CoroutineName = "coroutine" (default)
}
// Starts a new coroutine on Dispatchers.Default
val job2 = scope.launch(Dispatchers.Default + CoroutineName("BackgroundCoroutine")) {
// New coroutine with CoroutineName = "BackgroundCoroutine" (overridden)
}
}
}
Dodatkowe zasoby współprogramów
Aby uzyskać więcej zasobów o współudziałach, kliknij te linki: