In diesem Leitfaden werden die Erwartungen der Nutzer an den UI-Status und die verfügbaren Optionen zum Beibehalten des Status erläutert.
Es ist wichtig, den UI-Status schnell zu speichern und wiederherzustellen, nachdem das System die Hostaktivität oder den Anwendungsprozess beendet hat. Nutzer erwarten, dass der UI-Status gleich bleibt. Das System kann jedoch die Activity, die den Bildschirm hostet, und den darin gespeicherten Status löschen.
Um die Erwartungen der Nutzer und das Systemverhalten in Einklang zu bringen, verwenden Sie eine Kombination der folgenden Methoden:
ViewModel-Objekte- Gespeicherter Status in den folgenden Kontexten:
- Composables:
rememberSerializableundrememberSaveable - ViewModels:
SavedStateHandle.
- Composables:
- Lokaler Speicher, um den UI-Status bei App- und Bildschirmübergängen beizubehalten
Die optimale Lösung hängt von der Komplexität der UI-Daten, den Anwendungsfällen Ihrer App und dem Gleichgewicht zwischen Datenzugriffsgeschwindigkeit und Arbeitsspeichernutzung ab.
Ihre App sollte die Erwartungen der Nutzer erfüllen und eine schnelle, reaktionsfähige Benutzeroberfläche bieten. Vermeiden Sie Verzögerungen beim Laden von Daten in die Benutzeroberfläche, insbesondere nach häufigen Konfigurationsänderungen wie dem Drehen des Geräts.
Erwartungen der Nutzer und Systemverhalten
Je nach Aktion des Nutzers erwarten sie, dass der UI-Status entweder gelöscht oder beibehalten wird. In einigen Fällen tut das System automatisch, was der Nutzer erwartet. In anderen Fällen tut das System das Gegenteil.
Vom Nutzer initiierte Löschung des UI-Status
Der Nutzer erwartet, dass der temporäre UI-Status eines Bildschirms gleich bleibt, bis er ihn vollständig schließt. Der Nutzer kann einen Bildschirm oder eine App vollständig schließen, indem er Folgendes tut:
- Die App vom Bildschirm „Übersicht“ (Zuletzt verwendet) wischen
- Die App über den Bildschirm „Einstellungen“ beenden oder zwangsweise beenden
- Das Gerät neu starten
- Eine Art „Abschlussaktion“ ausführen (die von
Activity.finish()unterstützt wird)
In diesen Fällen geht der Nutzer davon aus, dass er den Bildschirm dauerhaft verlassen hat. Wenn er zurückkehrt, erwartet er, dass der Bildschirm in einem sauberen Zustand startet. Das zugrunde liegende Systemverhalten für diese Fälle entspricht den Erwartungen des Nutzers: Die Hostaktivitätsinstanz wird beendet und aus dem Arbeitsspeicher entfernt, zusammen mit allen darin gespeicherten Status und allen zugehörigen gespeicherten Statusdatensätzen.
Es gibt einige Ausnahmen von dieser Regel zum vollständigen Schließen. Beispielsweise erwartet ein Nutzer möglicherweise, dass er in einem Browser genau zu der Webseite weitergeleitet wird, die er sich angesehen hat, bevor er den Browser mit dem Button „Zurück“ verlassen hat.
Vom System initiierte Löschung des UI-Status
Ein Nutzer erwartet, dass der UI-Status eines Bildschirms bei einer Konfigurationsänderung wie dem Drehen des Geräts oder dem Wechsel in den Multifenstermodus gleich bleibt. Standardmäßig beendet das System jedoch die Hostaktivität, wenn eine solche Konfigurationsänderung auftritt, und löscht alle darin gespeicherten UI-Status. Weitere Informationen zu Gerätekonfigurationen finden Sie unter Auf Konfigurationsänderungen in Jetpack Compose reagieren.
Hinweis: Es ist möglich (aber nicht empfehlenswert), das Standardverhalten für Konfigurationsänderungen zu überschreiben. Weitere Informationen finden Sie unter Umgang mit Konfigurationsänderungen.
Ein Nutzer erwartet auch, dass der UI-Status Ihrer App gleich bleibt, wenn er vorübergehend zu einer anderen App wechselt und später zu Ihrer App zurückkehrt. Beispiel: Der Nutzer führt eine Suche auf einem Bildschirm aus und drückt dann die Home-Taste oder nimmt einen Anruf entgegen. Wenn er zum Suchbildschirm zurückkehrt, erwartet er, dass das Suchkeyword und die Ergebnisse noch genau wie zuvor vorhanden sind.
In diesem Fall wird Ihre App in den Hintergrund versetzt und das System versucht, den App-Prozess im Arbeitsspeicher zu behalten. Das System kann jedoch den Anwendungsprozess beenden, während der Nutzer mit anderen Apps interagiert. In diesem Fall wird die Hostaktivität zusammen mit allen darin gespeicherten Status beendet. Wenn der Nutzer die App neu startet, befindet sich der Bildschirm unerwartet in einem sauberen Zustand. Weitere Informationen zur Prozessbeendigung finden Sie unter Prozesse und App-Lebenszyklus.
Optionen zum Beibehalten des UI-Status
Wenn die Erwartungen des Nutzers an den UI-Status nicht mit dem Standardverhalten des Systems übereinstimmen, müssen Sie den UI-Status des Nutzers speichern und wiederherstellen, damit die vom System initiierte Beendigung für den Nutzer transparent ist.
Die einzelnen Optionen zum Beibehalten des UI-Status unterscheiden sich in den folgenden Dimensionen, die sich auf die Nutzererfahrung auswirken:
| ViewModel | Gespeicherter Status | Nichtflüchtiger Speicher | |
|---|---|---|---|
| Speicherort | im Arbeitsspeicher | im Arbeitsspeicher | auf der Festplatte oder im Netzwerk |
| Übersteht Konfigurationsänderungen | Ja | Ja | Ja |
| Übersteht vom System initiierte Prozessbeendigung | Nein | Ja | Ja |
Übersteht das vollständige Schließen des Bildschirms/finish() durch den Nutzer |
Nein | Nein | Ja |
| Datenbeschränkungen | Komplexe Objekte sind in Ordnung, aber der Speicherplatz ist durch den verfügbaren Arbeitsspeicher begrenzt. | Nur für primitive Typen und einfache, kleine Objekte wie String |
Nur durch Festplattenspeicher oder Kosten / Zeit für den Abruf aus der Netzwerkressource begrenzt |
| Lese-/Schreibzeit | Schnell (nur Arbeitsspeicherzugriff) | Langsam (erfordert Serialisierung/Deserialisierung) | Langsam (erfordert Festplattenzugriff oder Netzwerktransaktion) |
ViewModel zum Verarbeiten von Konfigurationsänderungen verwenden
ViewModel ist ideal zum Speichern und Verwalten von UI-bezogenen Daten, während der Nutzer die Anwendung aktiv verwendet. Es ermöglicht einen schnellen Zugriff auf UI-Daten und hilft Ihnen, Daten nicht immer wieder aus dem Netzwerk oder von der Festplatte abzurufen, wenn das Gerät gedreht wird, die Fenstergröße geändert wird oder andere häufig auftretende Konfigurationsänderungen auftreten. Informationen zum Implementieren eines ViewModel finden Sie im ViewModel-Leitfaden.
ViewModel behält die Daten im Arbeitsspeicher. Daher ist der Abruf günstiger als bei Daten von der Festplatte oder aus dem Netzwerk. Ein ViewModel ist mit einem Lebenszyklusinhaber verknüpft, z. B. einem Navigationsziel oder einer Aktivität. Es bleibt bei einer Konfigurationsänderung im Arbeitsspeicher und das System verknüpft das ViewModel automatisch mit der neuen Lebenszyklusinhaberinstanz, die sich aus der Konfigurationsänderung ergibt.
Im Gegensatz zum gespeicherten Status werden ViewModels bei einer vom System initiierten Prozessbeendigung beendet. Verwenden Sie
die SavedStateHandle API, um Daten nach einer vom System initiierten Prozessbeendigung in einem ViewModel neu zu laden. Wenn die Daten mit der Benutzeroberfläche verknüpft sind und nicht im ViewModel gespeichert werden müssen, verwenden Sie stattdessen rememberSerializable. Verwenden Sie für primitive Datentypen oder Szenarien, in denen Sie @Serializable nicht verwenden möchten, rememberSaveable. Wenn es sich bei den Daten um Anwendungsdaten handelt, ist es möglicherweise
besser, sie auf dem Laufwerk zu speichern.
Wenn Sie bereits eine In-Memory-Lösung zum Speichern des UI-Status bei Konfigurationsänderungen haben, müssen Sie ViewModel möglicherweise nicht verwenden.
Gespeicherten Status als Backup verwenden, um vom System initiierte Prozessbeendigungen zu verarbeiten
APIs wie rememberSerializable und rememberSaveable in Compose und
SavedStateHandle in ViewModels speichern die Daten, die zum Neuladen des UI
Status erforderlich sind, wenn das System eine Komponente beendet und später neu erstellt. Um komplexe Datenstrukturen effizienter zu verarbeiten, unterstützt SavedStateHandle die Kotlinx-Serialisierung über die saved {}-Erweiterung. So können Sie typsichere Objekte nahtlos zusammen mit standardmäßigen primitiven Typen beibehalten und wiederherstellen. Informationen zum Implementieren des gespeicherten Status mit rememberSaveable finden Sie unter Status und Jetpack
Compose.
Gespeicherte Status-Bundles bleiben sowohl bei Konfigurationsänderungen als auch bei Prozessbeendigungen erhalten, sind jedoch durch Speicher und Geschwindigkeit begrenzt, da die verschiedenen APIs Daten serialisieren. Die Serialisierung kann viel Arbeitsspeicher verbrauchen, wenn die serialisierten Objekte komplex sind. Da dieser Vorgang während einer Konfigurationsänderung im Hauptthread erfolgt, kann eine lange Serialisierung zu ausgelassenen Frames und visuellen Rucklern führen.
Verwenden Sie den gespeicherten Status nicht, um große Datenmengen wie Bitmaps oder komplexe Datenstrukturen zu speichern, die eine lange Serialisierung oder Deserialisierung erfordern. Speichern Sie stattdessen nur primitive Typen und einfache, kleine Objekte wie String. Verwenden Sie den gespeicherten Status also, um eine minimale Menge an Daten zu speichern, z. B. eine ID, um die Daten neu zu erstellen, die erforderlich sind, um die Benutzeroberfläche in ihren vorherigen Zustand zurückzuversetzen, falls die anderen Persistenzmechanismen fehlschlagen. Die meisten Apps sollten dies implementieren, um vom System initiierte Prozessbeendigungen zu verarbeiten.
Je nach Anwendungsfällen Ihrer App müssen Sie den gespeicherten Status möglicherweise gar nicht verwenden. Ein Browser kann den Nutzer beispielsweise genau zu der Webseite zurückbringen, die er sich angesehen hat, bevor er den Browser verlassen hat. Wenn sich Ihre Aktivität so verhält, können Sie auf den gespeicherten Status verzichten und stattdessen alles lokal beibehalten.
Wenn Sie außerdem eine Aktivität über eine Intent öffnen, wird das Bundle mit Extras sowohl bei Konfigurationsänderungen als auch bei der Wiederherstellung der Aktivität durch das System an die Aktivität gesendet. Wenn ein Teil der UI-Statusdaten, z. B. eine Suchanfrage, beim Starten der Aktivität als Intent-Extra übergeben wurde, können Sie das Extras-Bundle anstelle des gespeicherten Status-Bundles verwenden. Weitere Informationen zu Intent-Extras finden Sie unter Intent und Intent-Filter.
In beiden Szenarien sollten Sie trotzdem ein ViewModel verwenden, um zu vermeiden,
dass bei einer Konfigurationsänderung Zyklen für das Neuladen von Daten aus der Datenbank verschwendet werden.
In Fällen, in denen die beizubehaltenden UI-Daten einfach und klein sind, können Sie die APIs für den gespeicherten Status allein verwenden, um Ihre Statusdaten beizubehalten.
Mit SavedStateRegistry in den gespeicherten Status einbinden
Ab Fragment 1.1.0 oder der transitiven Abhängigkeit Activity
1.0.0 implementieren UI-Komponenten wie ComponentActivity
SavedStateRegistryOwner und stellen eine SavedStateRegistry bereit, die an diese Komponente gebunden ist. Mit SavedStateRegistry können Komponenten in den gespeicherten Status eingebunden werden, um ihn zu verwenden oder dazu beizutragen. Beispielsweise verwendet das Modul „Gespeicherter Status“ für ViewModel SavedStateRegistry, um ein
SavedStateHandle zu erstellen und es Ihren ViewModel-Objekten zur Verfügung zu stellen. Sie können
die SavedStateRegistry in Ihrem Lebenszyklusinhaber abrufen, indem Sie
savedStateRegistry aufrufen.
Komponenten, die zum gespeicherten Status beitragen, müssen
SavedStateRegistry.SavedStateProvider implementieren, das eine einzelne Methode
namens saveState() definiert. Mit der Methode saveState() kann Ihre Komponente ein Bundle zurückgeben, das alle Status enthält, die aus dieser Komponente gespeichert werden sollen.
SavedStateRegistry ruft diese Methode während der Phase zum Speichern des Status im Lebenszyklus des Lebenszyklusinhabers auf.
class SearchManager : SavedStateRegistry.SavedStateProvider {
companion object {
private const val QUERY = "query"
}
private val query: String? = null
...
override fun saveState(): Bundle {
return bundleOf(QUERY to query)
}
}
Rufen Sie registerSavedStateProvider() in
der SavedStateRegistry auf, um einen SavedStateProvider zu registrieren. Übergeben Sie dabei einen Schlüssel, der mit den Daten des Anbieters verknüpft werden soll, sowie den Anbieter. Die zuvor gespeicherten Daten für den Anbieter können aus dem gespeicherten Status abgerufen werden, indem Sie consumeRestoredStateForKey() in der SavedStateRegistry aufrufen und den Schlüssel übergeben, der mit den Daten des Anbieters verknüpft ist.
In einer ComponentActivity können Sie einen SavedStateProvider in
onCreate() registrieren, nachdem Sie super.onCreate() aufgerufen haben. Alternativ können Sie einen
LifecycleObserver für einen SavedStateRegistryOwner festlegen, der
LifecycleOwner implementiert, und den SavedStateProvider registrieren, sobald das Ereignis
ON_CREATE eintritt. Mit einem LifecycleObserver können Sie die Registrierung und den Abruf des zuvor gespeicherten Status von der SavedStateRegistryOwner selbst entkoppeln.
class SearchManager(registryOwner: SavedStateRegistryOwner) : SavedStateRegistry.SavedStateProvider {
companion object {
private const val PROVIDER = "search_manager"
private const val QUERY = "query"
}
private val query: String? = null
init {
// Register a LifecycleObserver for when the Lifecycle hits ON_CREATE
registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_CREATE) {
val registry = registryOwner.savedStateRegistry
// Register this object for future calls to saveState()
registry.registerSavedStateProvider(PROVIDER, this)
// Get the previously saved state and restore it
val state = registry.consumeRestoredStateForKey(PROVIDER)
// Apply the previously saved state
query = state?.getString(QUERY)
}
}
}
override fun saveState(): Bundle {
return bundleOf(QUERY to query)
}
...
}
class SearchActivity : ComponentActivity() {
private var searchManager = SearchManager(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set up your Compose UI here
setContent {
// ...
}
}
}
Lokale Persistenz verwenden, um Prozessbeendigungen für komplexe oder große Daten zu verarbeiten
Nichtflüchtiger lokaler Speicher wie eine Datenbank oder DataStore bleibt so lange erhalten, wie Ihre Anwendung auf dem Gerät des Nutzers installiert ist (es sei denn, der Nutzer löscht die Daten für Ihre App). Ein solcher lokaler Speicher übersteht zwar vom System initiierte Beendigungen des Anwendungsprozesses, der Abruf kann jedoch teuer sein, da er aus dem lokalen Speicher in den Arbeitsspeicher gelesen werden muss. Oft ist dieser nichtflüchtige lokale Speicher bereits Teil Ihrer Anwendungsarchitektur, um alle Daten zu speichern, die Sie nicht verlieren möchten, wenn Sie die App öffnen und schließen.
Weder ViewModel noch Status, der mit rememberSerializable, rememberSaveable oder SavedStateHandle gespeichert wurde, sind langfristige Speicherlösungen und daher kein Ersatz für lokalen Speicher wie eine Datenbank. Verwenden Sie diese Mechanismen stattdessen nur zum vorübergehenden Speichern des temporären UI-Status und verwenden Sie nichtflüchtigen Speicher für andere App-Daten. Weitere Informationen zur Nutzung des lokalen Speichers, um Ihre App-Modelldaten langfristig beizubehalten (z.B. bei Neustarts des Geräts), finden Sie im Leitfaden zur App-Architektur.
UI-Status verwalten: Teile und herrsche
Sie können den UI-Status effizient speichern und wiederherstellen, indem Sie die Arbeit auf die verschiedenen Arten von Persistenzmechanismen aufteilen. In den meisten Fällen sollte jeder dieser Mechanismen einen anderen Datentyp speichern, der in der App verwendet wird, basierend auf den Kompromissen zwischen Datenkomplexität, Zugriffsgeschwindigkeit und Lebensdauer:
- Lokale Persistenz: Speichert alle Anwendungsdaten, die Sie nicht verlieren möchten, wenn Sie die App öffnen und schließen.
- Beispiel: Eine Sammlung von Song-Objekten, die Audiodateien und Metadaten enthalten können.
ViewModel: Speichert im Arbeitsspeicher alle Daten, die zum Anzeigen der zugehörigen Benutzeroberfläche erforderlich sind, den UI-Status des Bildschirms.- Beispiel: Die Song-Objekte der letzten Suche und die letzte Suchanfrage.
- Gespeicherter Status (
rememberSerializable,rememberSaveableundSavedStateHandle): Speichert eine kleine Menge an Daten, die zum Neuladen des UI-Status erforderlich sind, wenn das System die Benutzeroberfläche beendet und dann neu erstellt. Anstatt hier komplexe Objekte zu speichern, sollten Sie die komplexen Objekte im lokalen Speicher beibehalten und eine eindeutige ID für diese Objekte in den APIs für den gespeicherten Status speichern.- Beispiel: Die letzte Suchanfrage speichern.
Nehmen wir als Beispiel eine App, mit der Sie Ihre Song-Bibliothek durchsuchen können. So sollten verschiedene Ereignisse verarbeitet werden:
Wenn der Nutzer einen Song hinzufügt, delegiert das ViewModel sofort die lokale Persistenz
dieser Daten. Wenn dieser neu hinzugefügte Song in der Benutzeroberfläche angezeigt werden soll, müssen Sie auch die Daten im ViewModel-Objekt aktualisieren, um das Hinzufügen des Songs widerzuspiegeln. Alle Datenbankeinfügungen müssen außerhalb des Hauptthreads erfolgen.
Wenn der Nutzer nach einem Song sucht, sollten alle komplexen Song-Daten, die Sie aus der Datenbank laden, sofort im ViewModel-Objekt als Teil des UI-Status des Bildschirms gespeichert werden.
Wenn die App in den Hintergrund versetzt wird und das System den Status speichert, sollte die Suchanfrage mit APIs für den gespeicherten Status gespeichert werden, falls der Prozess neu erstellt wird. Da die Informationen erforderlich sind, um Anwendungsdaten zu laden, die hier beibehalten werden, speichern Sie die Suchanfrage im ViewModel SavedStateHandle oder verwenden Sie rememberSerializable oder rememberSaveable in Ihren Composables. Das sind alle Informationen, die Sie benötigen, um die Daten zu laden und die Benutzeroberfläche wieder in den aktuellen Zustand zu versetzen.
Komplexe Status wiederherstellen: Teile zusammensetzen
Wenn der Nutzer zur App zurückkehrt, gibt es zwei mögliche Szenarien für die Neuerstellung der Benutzeroberfläche:
- Die Benutzeroberfläche wird neu erstellt, nachdem das System den Anwendungsprozess beendet hat. Die Abfrage wurde vom System mit APIs für den gespeicherten Status gespeichert. Das
ViewModel(mitSavedStateHandle) oder das Composable (mitrememberSerializableoderrememberSaveable) stellt die Abfrage automatisch wieder her. Wenn das Composable die Abfrage wiederherstellt, übergibt es sie an dasViewModel. DasViewModelstellt fest, dass keine Suchergebnisse im Cache gespeichert sind, und delegiert das Laden der Suchergebnisse mit der angegebenen Suchanfrage. - Die Benutzeroberfläche wird nach einer Konfigurationsänderung neu erstellt. Da die
ViewModel-Instanz nicht beendet wurde, sind alle Informationen im Cache desViewModelgespeichert und die Datenbank muss nicht noch einmal abgefragt werden.
Zusätzliche Ressourcen
Weitere Informationen zum Speichern von UI-Status finden Sie in den folgenden Ressourcen.
- Dokumentation zur UI-Ebene
- UI-Status in Compose speichern
- Dokumentation zu Status und Jetpack Compose
Codelabs
Ansichteninhalte
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Modul für den gespeicherten Status für ViewModel
- Lebenszyklen mit lebenszyklusbezogenen Komponenten verarbeiten
- ViewModel – Übersicht