Kolumna to wzorzec projektowania równoczesności, którego możesz używać na Androidzie, aby uprościć kod wykonywany asynchronicznie. Korutyny zostały dodane do Kotlin w wersji 1.3 i są oparte na pojęcia z innych języków.
Na Androidzie współprogramy pomagają zarządzać długotrwałymi zadaniami, które mogą w przeciwnym razie zablokuje wątek główny i spowoduje, że aplikacja przestanie odpowiadać. Ponad 50% profesjonalnych programistów, którzy korzystają z komponentów, zaobserwowało, większa produktywność. W tym artykule opisujemy, jak za pomocą współrzędnych Kotlina radzić sobie z tymi co pozwoli Ci pisać bardziej przejrzysty i zwięzły kod aplikacji.
Funkcje
Coroutines to nasze zalecane rozwiązanie do programowania asynchronicznego na na urządzeniu z Androidem. Funkcje, na które warto zwrócić uwagę:
- Prosty: w 1 wątku można uruchomić wiele współrzędnych ze względu na: pomoc dotycząca zawieszenie, co nie blokuje wątku, w którym działa współrzędna. Zawieszam oszczędza pamięć nad blokowaniem przy jednoczesnej obsłudze wielu operacji równoczesnych.
- Mniej wycieków pamięci: użyj ustrukturyzowana równoczesność do uruchamiania operacji w zakresie.
- Wbudowana funkcja anulowania: Anulowanie jest rozpowszechniany automatycznie przez uruchomioną hierarchię współprogramów.
- Integracja z Jetpackiem: wiele bibliotek Jetpack to np. rozszerzenia, które zapewniają pełną obsługę współprogramów. Niektóre biblioteki również udostępniają własne zakres wspólny, dla uporządkowanej równoczesności.
Przegląd przykładów
Przykłady znajdziesz w Przewodniku po architekturze aplikacji. wyślij żądanie sieciowe i zwróć wynik do głównej funkcji w wątku, w którym aplikacja może wyświetlić wynik użytkownikowi.
Konkretnie: ViewModel
Komponent architektury wywołuje warstwę repozytorium w wątku głównym do
uruchamiać żądanie sieciowe. Ten przewodnik przedstawia różne rozwiązania
które korzystają z współdziału, aby wątek główny pozostawał odblokowany.
ViewModel
zawiera zestaw rozszerzeń KTX, które współpracują bezpośrednio z
współrzędne. Te rozszerzenia są
bibliotekę lifecycle-viewmodel-ktx
, w której są używane
w tym przewodniku.
Informacje o zależności
Aby używać współrzędnych w projekcie Androida, dodaj tę zależność
do pliku build.gradle
aplikacji:
Odlotowe
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
Wykonuję w wątku w tle
Wysłanie żądania sieciowego w wątku głównym powoduje, że czeka się, czyli blokuje.
aż do otrzymania odpowiedzi. Ponieważ wątek jest zablokowany, system operacyjny nie
może wywołać metodę onDraw()
, co powoduje, że aplikacja zawiesza się i może
wyświetla się okno Aplikacja nie odpowiada (ANR). Dla użytkowników
, uruchomimy tę operację w wątku w tle.
Najpierw przyjrzymy się naszym zajęciom (Repository
) i zobaczmy, jak są
wysyłając żądanie sieciowe:
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
// Function that makes the network request, blocking the current thread
fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
Działanie makeLoginRequest
jest synchroniczne i blokuje wątek wywołujący. Do modelu
odpowiedź na żądanie sieciowe, mamy własną klasę Result
.
ViewModel
wyzwala żądanie sieciowe, gdy użytkownik kliknie, w przypadku:
przykład na przycisku:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
W przypadku poprzedniego kodu LoginViewModel
blokuje wątek UI, gdy
wysyłając żądanie sieciowe. Najprostsze rozwiązanie, które przeniesie wykonanie
w wątku głównym jest utworzenie nowej współrzędni i uruchomienie sieci
dla żądania w wątku I/O:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
Przeanalizujmy kod współrzędnych w funkcji login
:
viewModelScope
to wstępnie zdefiniowany elementCoroutineScope
, który wchodzi w składViewModel
rozszerzenia KTX. Pamiętaj, że wszystkie współprogramy muszą być uruchamiane zakresu.CoroutineScope
zarządza co najmniej 1 powiązanymi współprogramami.launch
to funkcja, która tworzy współrzędną i wysyła wykonania treści funkcji odpowiedniemu dyspozytorowi.Dispatchers.IO
oznacza, że współprogram powinien zostać uruchomiony na zarezerwowany dla operacji wejścia-wyjścia.
Funkcja login
jest wykonywana w ten sposób:
- Aplikacja wywołuje funkcję
login
z warstwyView
w wątku głównym. launch
tworzy nową współrzędną i wysyłane jest żądanie sieciowe niezależnie w wątku zarezerwowanym na potrzeby operacji wejścia-wyjścia.- Gdy współprogram jest uruchomiony, funkcja
login
kontynuuje wykonywanie i wraca, prawdopodobnie jeszcze przed zakończeniem żądania sieciowego. Pamiętaj, że dla uproszczenia odpowiedź sieciowa jest na razie ignorowana.
Ponieważ współprogram został uruchomiony w klastrze viewModelScope
, jest on wykonywany w
zakres usługi ViewModel
. Jeśli ViewModel
zostanie zniszczony, ponieważ
użytkownik opuszcza ekran, viewModelScope
jest automatycznie
anulowane i wszystkie uruchomione współprogramy również są anulowane.
Problem z poprzednim przykładem polega na tym, że wszystkie połączenia
makeLoginRequest
musi pamiętać o wyłączeniu wykonania
w wątku głównym. Zobaczmy, jak można zmodyfikować Repository
, aby rozwiązać
tego problemu.
Używaj wspólnych reguł na potrzeby głównego bezpieczeństwa
Funkcja jest uznawana za główną, gdy nie blokuje aktualizacji interfejsu
w wątku głównym. Funkcja makeLoginRequest
nie jest bezpieczna, ponieważ wywołanie
makeLoginRequest
z wątku głównego blokuje interfejs użytkownika. Użyj
Funkcja withContext()
z biblioteki współrzędnych, aby przenieść wykonanie
o współdziałaniu z innym wątkiem:
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}
withContext(Dispatchers.IO)
przenosi wykonanie współrzędnej do
dzięki czemu funkcja wywoływania jest bezpieczna, a UI
w razie potrzeby.
Adres makeLoginRequest
jest również oznaczony słowem kluczowym suspend
. To słowo kluczowe
to sposób wymuszenia funkcji Kotlina, która zostanie wywołana z poziomu współrzędu.
W poniższym przykładzie współrzędna jest tworzona w tabeli LoginViewModel
.
Gdy makeLoginRequest
przenosi wykonanie z wątku głównego, współrzędna
funkcji login
można teraz wykonać w wątku głównym:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
Zwróć uwagę, że współrzędna jest tu nadal potrzebna, ponieważ makeLoginRequest
jest
funkcji suspend
, a wszystkie funkcje suspend
muszą być wykonywane w
współpracą.
Ten kod różni się od poprzedniego przykładu kodu login
na kilka sposobów:
launch
nie przyjmuje parametruDispatchers.IO
. W czasie przekazaćDispatcher
dolaunch
, wszystkie współprogramy uruchomione od DziałanieviewModelScope
jest uruchamiane w wątku głównym.- Wynik żądania sieciowego jest teraz obsługiwany w celu wyświetlenia powodzenia lub interfejsu błędu.
Funkcja logowania uruchamia się teraz w ten sposób:
- Aplikacja wywołuje funkcję
login()
z warstwyView
w wątku głównym. launch
tworzy nową współrzędną w wątku głównym i rozpoczyna się współrzędna- W ramach współrzędu wywołanie funkcji
loginRepository.makeLoginRequest()
teraz zawiesza dalsze wykonanie współrzędnej dowithContext
blok w:makeLoginRequest()
kończy się biegiem. - Gdy blok
withContext
się zakończy, współrzędna wlogin()
jest wznawiana wykonanie w wątku głównym z wynikiem żądania sieciowego.
Obsługa wyjątków
Aby obsługiwać wyjątki, które może zgłaszać warstwa Repository
, użyj metody Kotlin
wbudowaną obsługę wyjątków.
W poniższym przykładzie używamy bloku try-catch
:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
val result = try {
loginRepository.makeLoginRequest(jsonBody)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
W tym przykładzie każdy nieoczekiwany wyjątek zgłoszony przez funkcję makeLoginRequest()
jest obsługiwane w interfejsie jako błąd.
Dodatkowe zasoby współprogramów
Bardziej szczegółowe informacje o współrzędnych na Androidzie znajdziesz w artykule Zwiększanie wydajności aplikacji dzięki współrzędnym Kotlin
Aby uzyskać więcej zasobów o współudziałach, kliknij te linki:
- Omówienie konwersji (JetBrains)
- Przewodnik po korytynach (JetBrains)
- Dodatkowe materiały na temat współrzędnych i przepływu Kotlin