Eine Koroutine ist ein Designmuster für Gleichzeitigkeit, das Android vereinfacht Code, der asynchron ausgeführt wird. Coroutinen wurden in Version 1.3 zu Kotlin hinzugefügt und basieren auf Konzepten aus anderen Sprachen.
Auf Android können Sie mit gemeinsamen Abläufen lange Aufgaben, die möglicherweise den Hauptthread ansonsten blockieren und dazu führen, dass Ihre App nicht mehr reagiert. Mehr als 50% der professionellen Entwickler, die gemeinsame Routinen verwenden, geben an, gesteigerte Produktivität. In diesem Thema wird beschrieben, wie Sie diese mit Kotlin-Coroutinen angehen. sodass Sie saubereren und präziseren App-Code schreiben können.
Funktionen
Koroutinen ist unsere empfohlene Lösung für die asynchrone Programmierung auf Android Beachten Sie unter anderem Folgendes:
- Einfach: Sie können mehrere Koroutinen in einem einzelnen Thread ausführen. Unterstützung für Sperrung, Dadurch wird der Thread, in dem die Koroutine ausgeführt wird, nicht blockiert. Wird ausgesetzt Das spart Arbeitsspeicher über die Blockierung und unterstützt gleichzeitig viele gleichzeitige Vorgänge.
- Weniger Speicherlecks: Verwenden Sie strukturierte Nebenläufigkeit um Vorgänge in einem bestimmten Bereich auszuführen.
- Integrierte Unterstützung bei Kündigungen: Kündigung wird automatisch durch die Hierarchie der laufenden Koroutinen weitergegeben.
- Jetpack-Integration: Viele Jetpack-Bibliotheken enthalten Erweiterungen, die vollständige Unterstützung für Koroutinen bieten. Einige Bibliotheken bieten auch eigene Coroutine-Bereich, den Sie für strukturierte Nebenläufigkeit.
Beispiele – Übersicht
Die Beispiele basieren auf dem Leitfaden zur App-Architektur. in diesem Thema eine Netzwerkanfrage stellen und das Ergebnis an die Haupt- -Thread, in dem die App dem Nutzer das Ergebnis anzeigen kann.
Konkret gilt: Die ViewModel
Die Architekturkomponente ruft die Repository-Ebene im Hauptthread auf,
die Netzwerkanfrage auslösen. In diesem Leitfaden werden verschiedene Lösungen
die Koroutinen verwenden, um die Blockierung des Hauptthreads aufzuheben.
ViewModel
umfasst eine Reihe von KTX-Erweiterungen, die direkt mit
Koroutinen. Diese Erweiterungen sind
lifecycle-viewmodel-ktx
und werden verwendet
in diesem Leitfaden.
Informationen zu Abhängigkeiten
Fügen Sie die folgende Abhängigkeit hinzu, um Koroutinen in Ihrem Android-Projekt zu verwenden
in die Datei build.gradle
Ihrer App ein:
Cool
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
Wird in einem Hintergrundthread ausgeführt
Eine Netzwerkanfrage an den Hauptthread führt dazu, dass dieser wartet bzw. blockiert,
bis eine Antwort eingeht. Da der Thread blockiert ist,
onDraw()
aufrufen. Dies führt dazu, dass deine App einfriert und möglicherweise
das Dialogfeld „Application Not Responding (ANR)“ (App antwortet nicht) öffnet. Für bessere Nutzer
führen wir diesen Vorgang
in einem Hintergrundthread aus.
Sehen wir uns zuerst den Repository
-Kurs an.
Netzwerkanfrage senden:
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"))
}
}
makeLoginRequest
ist synchron und blockiert den aufrufenden Thread. Zum Modell
der Antwort der Netzwerkanfrage haben wir unsere eigene Result
-Klasse.
Die ViewModel
löst die Netzwerkanfrage aus, wenn der Nutzer klickt, für
Beispiel für eine Schaltfläche:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
Mit dem vorherigen Code blockiert LoginViewModel
den UI-Thread, wenn
die die Netzwerkanfrage stellen. Die einfachste Lösung, um die Ausführung zu verschieben
aus dem Hauptthread zu entfernen ist, eine neue Koroutine zu erstellen und das Netzwerk
Anfrage zu einem E/A-Thread:
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)
}
}
}
Sehen wir uns den Code der Koroutinen in der Funktion login
an:
viewModelScope
ist eine vordefinierteCoroutineScope
, die inViewModel
KTX-Erweiterungen. Beachten Sie, dass alle Koroutinen in einem Umfang. EinCoroutineScope
verwaltet eine oder mehrere zugehörige Koroutinen.launch
ist eine Funktion, die eine Koroutine erstellt und die Funktion Ausführung seines Funktionskörpers an den entsprechenden Dispatcher.Dispatchers.IO
gibt an, dass diese Koroutine in einer Thread ist für E/A-Vorgänge reserviert.
Die Funktion login
wird so ausgeführt:
- Die App ruft die Funktion
login
über dieView
-Ebene im Hauptthread auf. launch
erstellt eine neue Koroutine und die Netzwerkanfrage wird gestellt unabhängig in einem Thread, der für E/A-Vorgänge reserviert ist.- Während die Koroutine ausgeführt wird, wird die Ausführung der Funktion
login
fortgesetzt. und zurückgegeben, möglicherweise noch bevor die Netzwerkanfrage abgeschlossen ist. Beachten Sie, dass der Einfachheit halber wird die Netzwerkantwort vorerst ignoriert.
Da diese Koroutine mit viewModelScope
gestartet wird, wird sie in ausgeführt in
den Geltungsbereich des ViewModel
. Wenn ViewModel
gelöscht wird, weil der
verwendet, wird viewModelScope
automatisch
und alle laufenden Koroutinen werden ebenfalls abgebrochen.
Ein Problem beim vorherigen Beispiel ist,
makeLoginRequest
muss daran denken, die Ausführung explizit zu verschieben
im Hauptthread. Sehen wir uns an, wie wir Repository
ändern können, um
dieses Problem zu lösen.
Koroutinen für die Hauptsicherheit verwenden
Wir betrachten eine Funktion als main-safe, wenn sie Aktualisierungen der Benutzeroberfläche im
im Hauptthread. Die Funktion makeLoginRequest
ist nicht hauptsicher, da das Aufrufen von
makeLoginRequest
aus dem Hauptthread blockiert die UI. Verwenden Sie die Methode
Funktion withContext()
aus der coroutines-Bibliothek, um die Ausführung zu verschieben
einer Koroutine zu einem anderen Thread hinzufügen:
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)
verschiebt die Ausführung der Koroutine in eine
E/A-Thread, wodurch die aufrufende Funktion hauptsicher wird und die Benutzeroberfläche
bei Bedarf aktualisieren.
makeLoginRequest
ist auch mit dem Keyword suspend
gekennzeichnet. Dieses Keyword
ist die Methode von Kotlin, mit der eine Funktion erzwungen wird, die innerhalb einer Koroutine aufgerufen wird.
Im folgenden Beispiel wird die Koroutine in LoginViewModel
erstellt.
Wenn makeLoginRequest
die Ausführung aus dem Hauptthread verschiebt, wird die Koroutine
in der Funktion login
kann jetzt im Hauptthread ausgeführt werden:
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
}
}
}
}
Die Koroutine ist hier weiterhin erforderlich, da makeLoginRequest
eine suspend
-Funktion; alle suspend
-Funktionen müssen in
eine Koroutine.
Dieser Code unterscheidet sich in einigen Punkten vom vorherigen login
-Beispiel:
launch
akzeptiert keinenDispatchers.IO
-Parameter. Wenn Sie nichtDispatcher
anlaunch
übergeben, alle Koroutinen, die abviewModelScope
wird im Hauptthread ausgeführt.- Das Ergebnis der Netzwerkanfrage wird nun verarbeitet, um den Erfolg Fehler der Benutzeroberfläche.
Die Log-in-Funktion wird jetzt wie folgt ausgeführt:
- Die App ruft die Funktion
login()
über dieView
-Ebene im Hauptthread auf. launch
erstellt eine neue Koroutine im Hauptthread und die Koroutine beginnt Ausführung.- Innerhalb der Koroutine führt der Aufruf von
loginRepository.makeLoginRequest()
sperrt jetzt die weitere Ausführung der Koroutine biswithContext
Der Block inmakeLoginRequest()
ist fertig. - Sobald der
withContext
-Block abgeschlossen ist, wird die Koroutine inlogin()
fortgesetzt. Ausführung im Hauptthread mit dem Ergebnis der Netzwerkanfrage
Umgang mit Ausnahmen
Zur Verarbeitung von Ausnahmen, die von der Ebene Repository
ausgelöst werden können, verwenden Sie die
integrierte Unterstützung für Ausnahmen.
Im folgenden Beispiel verwenden wir einen try-catch
-Block:
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
}
}
}
}
In diesem Beispiel wird jede unerwartete Ausnahme, die vom makeLoginRequest()
wird in der Benutzeroberfläche als Fehler behandelt.
Zusätzliche Ressourcen für Koroutinen
Detailliertere Informationen zu gemeinsamen Abläufen auf Android findest du unter App-Leistung mit Kotlin-Koroutinen verbessern
Weitere Ressourcen zu Koroutinen finden Sie unter den folgenden Links:
- Übersicht über Coroutines (JetBrains)
- Leitfaden zu Coroutines (JetBrains)
- Zusätzliche Ressourcen für Kotlin-Koroutinen und -Ablauf