Kotlin na Androidzie

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:

dependencies {
    implementation
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}
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 element CoroutineScope, który wchodzi w skład ViewModel 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 warstwy View 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 parametru Dispatchers.IO. W czasie przekazać Dispatcher do launch, wszystkie współprogramy uruchomione od Działanie viewModelScope 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 warstwy View 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 do withContext blok w: makeLoginRequest() kończy się biegiem.
  • Gdy blok withContext się zakończy, współrzędna w login() 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: