Kotlin-Koroutinen stellen eine API zum Schreiben von asynchronem Code bereit. Mit Kotlin-Koroutinen können Sie eine CoroutineScope
definieren, um zu steuern, wann die Koroutinen ausgeführt werden sollen. Jeder asynchrone Vorgang wird in einem bestimmten Bereich ausgeführt.
Lebenszyklussensitive Komponenten bieten erstklassigen Support für Koroutinen für logische Bereiche in Ihrer App sowie eine Interoperabilitätsebene mit LiveData
.
In diesem Thema wird erläutert, wie Koroutinen effektiv mit Komponenten verwendet werden, die den Lebenszyklus berücksichtigen.
KTX-Abhängigkeiten hinzufügen
Die in diesem Artikel beschriebenen integrierten Koroutinenbereiche sind in den KTX-Erweiterungen für die jeweilige Komponente enthalten. Achten Sie darauf, die entsprechenden Abhängigkeiten hinzuzufügen, wenn Sie diese Bereiche verwenden.
- Verwenden Sie für
ViewModelScope
androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0
oder höher. - Verwenden Sie für
LifecycleScope
androidx.lifecycle:lifecycle-runtime-ktx:2.4.0
oder höher. - Verwenden Sie für
liveData
androidx.lifecycle:lifecycle-livedata-ktx:2.4.0
oder höher.
Lebenszyklussensitive Koroutinenbereiche
Lebenszyklussensitive Komponenten definieren die folgenden integrierten Bereiche, die Sie in Ihrer Anwendung verwenden können.
Modellumfang ansehen
Für jedes ViewModel
in der Anwendung ist eine ViewModelScope
definiert. Alle in diesem Bereich gestarteten Koroutinen werden automatisch abgebrochen, wenn die ViewModel
gelöscht wird. Koroutinen sind hier nützlich, wenn Sie Arbeiten haben, die nur ausgeführt werden müssen, wenn ViewModel
aktiv ist. Wenn Sie beispielsweise Daten für ein Layout berechnen, sollten Sie die Arbeit dem ViewModel
zuordnen. Wenn ViewModel
gelöscht wird, wird die Arbeit automatisch abgebrochen, um die Ressourcennutzung zu vermeiden.
Sie können auf den CoroutineScope
einer ViewModel
über das Attribut viewModelScope
von ViewModel zugreifen, wie im folgenden Beispiel gezeigt:
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
Lebenszyklusbereich
Für jedes Lifecycle
-Objekt wird ein LifecycleScope
definiert. Jede in diesem Bereich gestartete Koroutine wird abgebrochen, wenn Lifecycle
gelöscht wird. Sie können auf den CoroutineScope
von Lifecycle
entweder über die Property lifecycle.coroutineScope
oder lifecycleOwner.lifecycleScope
zugreifen.
Das folgende Beispiel zeigt, wie Sie mit lifecycleOwner.lifecycleScope
asynchron vorberechneten Text erstellen:
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
Neustartfähige lebenszyklusbewusste Koroutinen
lifecycleScope
bietet eine geeignete Möglichkeit, lang andauernde Vorgänge automatisch abzubrechen, wenn Lifecycle
den Wert DESTROYED
hat. Es kann aber auch vorkommen, dass Sie die Ausführung eines Codeblocks starten möchten, wenn sich Lifecycle
in einem bestimmten Zustand befindet, und abbrechen möchte, wenn er sich in einem anderen Status befindet. Sie können beispielsweise einen Ablauf erfassen, wenn Lifecycle
den Wert STARTED
hat, und die Sammlung abbrechen, wenn STOPPED
festgelegt ist. Dieser Ansatz verarbeitet die Flussemissionen nur, wenn die Benutzeroberfläche auf dem Bildschirm sichtbar ist. Dadurch werden Ressourcen gespart und möglicherweise App-Abstürze vermieden.
In diesen Fällen stellen Lifecycle
und LifecycleOwner
die repeatOnLifecycle
API zum Sperren bereit, die genau dies tut. Das folgende Beispiel enthält einen Codeblock, der immer dann ausgeführt wird, wenn das verknüpfte Lifecycle
mindestens den Status STARTED
hat, und wird abgebrochen, wenn Lifecycle
den Status STOPPED
hat:
class MyFragment : Fragment() {
val viewModel: MyViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Create a new coroutine in the lifecycleScope
viewLifecycleOwner.lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// This happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
viewModel.someDataFlow.collect {
// Process item
}
}
}
}
}
Lebenszyklussensitive Flow-Sammlung
Wenn Sie nur eine Erfassung des Lebenszyklus bei einem einzelnen Ablauf durchführen müssen, können Sie den Code mit der Methode Flow.flowWithLifecycle()
vereinfachen:
viewLifecycleOwner.lifecycleScope.launch {
exampleProvider.exampleFlow()
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
Wenn Sie jedoch eine Erfassung des Lebenszyklus in mehreren Abläufen parallel ausführen müssen, müssen Sie jeden Ablauf in verschiedenen Koroutinen erfassen. In diesem Fall ist es effizienter, repeatOnLifecycle()
direkt zu verwenden:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Because collect is a suspend function, if you want to
// collect multiple flows in parallel, you need to do so in
// different coroutines.
launch {
flow1.collect { /* Process the value. */ }
}
launch {
flow2.collect { /* Process the value. */ }
}
}
}
Lebenszyklusbewusste Koroutinen aussetzen
Auch wenn der CoroutineScope
eine geeignete Methode zum automatischen Abbrechen von Vorgängen mit langer Ausführungszeit bietet, gibt es möglicherweise andere Fälle, in denen Sie die Ausführung eines Codeblocks anhalten möchten, wenn sich Lifecycle
nicht in einem bestimmten Status befindet. Wenn Sie beispielsweise ein FragmentTransaction
ausführen möchten, müssen Sie warten, bis Lifecycle
mindestens den Wert STARTED
hat. In diesen Fällen bietet Lifecycle
zusätzliche Methoden: lifecycle.whenCreated
, lifecycle.whenStarted
und lifecycle.whenResumed
. Jede innerhalb dieser Blöcke ausgeführte Koroutine wird ausgesetzt, wenn sich Lifecycle
nicht zumindest im gewünschten Mindestzustand befindet.
Das folgende Beispiel enthält einen Codeblock, der nur ausgeführt wird, wenn das verknüpfte Lifecycle
mindestens den Status STARTED
hat:
class MyFragment: Fragment {
init { // Notice that we can safely launch in the constructor of the Fragment.
lifecycleScope.launch {
whenStarted {
// The block inside will run only when Lifecycle is at least STARTED.
// It will start executing when fragment is started and
// can call other suspend methods.
loadingView.visibility = View.VISIBLE
val canAccess = withContext(Dispatchers.IO) {
checkUserAccess()
}
// When checkUserAccess returns, the next line is automatically
// suspended if the Lifecycle is not *at least* STARTED.
// We could safely run fragment transactions because we know the
// code won't run unless the lifecycle is at least STARTED.
loadingView.visibility = View.GONE
if (canAccess == false) {
findNavController().popBackStack()
} else {
showContent()
}
}
// This line runs only after the whenStarted block above has completed.
}
}
}
Wenn die Lifecycle
gelöscht wird, während eine Koroutine über eine der Methoden when
aktiv ist, wird die Koroutine automatisch abgebrochen. Im folgenden Beispiel wird der Block finally
ausgeführt, sobald der Status Lifecycle
auf DESTROYED
gesetzt ist:
class MyFragment: Fragment {
init {
lifecycleScope.launchWhenStarted {
try {
// Call some suspend functions.
} finally {
// This line might execute after Lifecycle is DESTROYED.
if (lifecycle.state >= STARTED) {
// Here, since we've checked, it is safe to run any
// Fragment transactions.
}
}
}
}
}
Koroutinen mit LiveData verwenden
Bei Verwendung von LiveData
müssen Sie Werte möglicherweise asynchron berechnen. Sie können beispielsweise die Einstellungen eines Nutzers abrufen und in Ihrer UI bereitstellen. In diesen Fällen können Sie mit der Builder-Funktion liveData
eine suspend
-Funktion aufrufen und das Ergebnis als LiveData
-Objekt bereitstellen.
Im folgenden Beispiel ist loadUser()
eine Anhaltefunktion, die an anderer Stelle deklariert wurde. Verwenden Sie die Builder-Funktion liveData
, um loadUser()
asynchron aufzurufen, und geben Sie dann mit emit()
das Ergebnis aus:
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
Der Baustein liveData
dient als strukturierte Primitive für die Nebenläufigkeit zwischen Koroutinen und LiveData
. Der Codeblock wird ausgeführt, wenn LiveData
aktiv wird, und nach einem konfigurierbaren Zeitlimit automatisch abgebrochen, wenn LiveData
inaktiv wird. Wird er vor dem Abschluss abgebrochen, wird er neu gestartet, wenn LiveData
wieder aktiv wird. Wenn er bei einer vorherigen Ausführung erfolgreich abgeschlossen wurde, wird er nicht neu gestartet. Er wird nur neu gestartet, wenn er automatisch abgebrochen wird. Wenn der Block aus einem anderen Grund abgebrochen wird (z.B. durch Auslösen eines CancellationException
), wird er nicht neu gestartet.
Sie können auch mehrere Werte aus dem Block ausgeben. Jeder emit()
-Aufruf unterbricht die Ausführung des Blocks, bis der Wert LiveData
im Hauptthread festgelegt wird.
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
Sie können liveData
auch mit Transformations
kombinieren. Hier ein Beispiel:
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
Sie können mehrere Werte aus einer LiveData
ausgeben. Rufen Sie dazu die Funktion emitSource()
auf, wann immer Sie einen neuen Wert ausgeben möchten. Bei jedem Aufruf von emit()
oder emitSource()
wird die zuvor hinzugefügte Quelle entfernt.
class UserDao: Dao {
@Query("SELECT * FROM User WHERE id = :id")
fun getUser(id: String): LiveData<User>
}
class MyRepository {
fun getUser(id: String) = liveData<User> {
val disposable = emitSource(
userDao.getUser(id).map {
Result.loading(it)
}
)
try {
val user = webservice.fetchUser(id)
// Stop the previous emission to avoid dispatching the updated user
// as `loading`.
disposable.dispose()
// Update the database.
userDao.insert(user)
// Re-establish the emission with success type.
emitSource(
userDao.getUser(id).map {
Result.success(it)
}
)
} catch(exception: IOException) {
// Any call to `emit` disposes the previous one automatically so we don't
// need to dispose it here as we didn't get an updated value.
emitSource(
userDao.getUser(id).map {
Result.error(exception, it)
}
)
}
}
}
Weitere Informationen zu Koroutinen finden Sie unter den folgenden Links:
- Anwendungsleistung mit Kotlin-Koroutinen verbessern
- Coroutinen – Übersicht
- Threading in CoroutineWorker
Weitere Informationen
Weitere Informationen zur Verwendung von Koroutinen mit Komponenten, die den Lebenszyklus berücksichtigen, finden Sie in den folgenden zusätzlichen Ressourcen.
Produktproben
Blogs
- Koroutinen unter Android: Anwendungsmuster
- Einfache Koroutinen in Android: viewModelScope
- Zwei aufeinanderfolgende LiveData-Emissionen in Koroutinen testen
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- LiveData – Übersicht
- Lebenszyklen mit lebenszyklusbewussten Komponenten handhaben
- Auslagerungsdaten laden und anzeigen