Konfigurationsänderungen in Android: So vermeidet ihr Datenverlust und frustrierte User
Jeder von uns kennt den Schreckmoment: Man tippt gerade eine längere Nachricht in einem Formular, dreht das Handy kurz, weil man es sich am Stehtisch im Café Central bequemer machen will, und zack – der ganze Fortschritt ist futsch. Was für uns wie ein simpler Bedienfehler aussieht, ist technisch ein kompletter Neustart der Activity. Wir erklären, warum das passiert und wie wir unsere Apps so bauen, dass dieser Frust der Vergangenheit angehört.
Was passiert eigentlich bei einer Konfigurationsänderung?
Stellt euch vor, ihr checkt gerade die Öffnungszeiten des Stephansdoms auf eurem Samsung Galaxy S24. Ihr dreht das Gerät vom Hoch- ins Querformat, um den Stadtplan besser zu sehen. Für Android ist das kein sanfter Übergang, sondern ein harter Cut: Das System zerstört die laufende Activity komplett und erzeugt sie neu, damit sie die passenden Ressourcen für die neue Bildschirmausrichtung laden kann. Der Zyklus ist gnadenlos effizient: onPause(), onStop(), onDestroy() – und unmittelbar danach startet eine frische Instanz mit onCreate(), onStart() und onResume(). Alle im Speicher gehaltenen Objekte der alten Instanz gehen dabei verloren, wenn wir keine Vorkehrungen treffen.
Viele denken bei diesem Problem nur ans Drehen des Bildschirms, aber die Liste der Auslöser ist länger:
- Bildschirmdrehung: Der häufigste Fall – Wechsel zwischen Hoch- und Querformat
- Tastaturanschluss: Wenn ein User eine physische Tastatur ansteckt oder abzieht
- Sprachwechsel: Änderung der Systemsprache während die App läuft
- Dark Mode: Aktivierung oder Deaktivierung des dunklen Erscheinungsbilds
- Netzwerkwechsel: Sogar das Wechseln des Mobilfunknetzes in der Wiener U-Bahn kann in bestimmten Szenarien eine Rolle spielen
- Multi-Window-Modus: Wenn der Nutzer die App in den geteilten Bildschirm zieht
- Schriftgrößenänderung: Anpassung der Systemschriftgröße in den Einstellungen
Wir müssen unsere Architektur also breiter aufstellen und dürfen uns nicht nur auf den klassischen Dreher verlassen.
Der klassische Fehler: Verlorene Daten in AsyncTask und ListView
In älteren Android-Tutorials war die Kombination aus einer AsyncTask für Hintergrundarbeit und einer ListView zur Anzeige der Daten der Standard. Genau hier lauert die Falle. Während die AsyncTask im Hintergrund noch Daten aus dem Web holt, wird die Activity bei einer Drehung zerstört. Die AsyncTask hält aber eine Referenz auf die alte, nun tote Activity und versucht, auf deren UI-Elemente zuzugreifen. Das Ergebnis ist ein Crash oder ein Memory Leak, weil die Garbage Collection die Activity nicht freigeben kann. Scrollt ein Nutzer in der U6 zwischen Josefstädter Straße und Handelskai durch eine lange Liste, verliert er nach einem kurzen Wackler nicht nur seine Position, sondern riskiert eine komplett eingefrorene App.
Die typischen Symptome dieses Problems sind vielfältig:
- NullPointerException: Die AsyncTask referenziert UI-Elemente, die bereits zerstört wurden
- Memory Leaks: Die alte Activity kann nicht vom Garbage Collector freigegeben werden, weil die AsyncTask noch eine Referenz hält
- Verlorene Scroll-Position: Der Nutzer findet sich nach der Drehung wieder am Anfang der Liste
- Doppelte Netzwerkanfragen: Die neue Activity-Instanz startet einen weiteren Request, während der alte noch läuft
Der schnelle, aber schmutzige Workaround ist das Hinzufügen von android:configChanges="orientation|screenSize" im Manifest. Damit unterdrücken wir den Neustart der Activity. Das fühlt sich im ersten Moment gut an, ist aber wie ein Pflaster auf einer tieferen Wunde. Sobald ein anderer Konfigurationswechsel eintritt oder das System die App im Hintergrund aus Speichermangel beendet, sind die Daten trotzdem weg. Wir raten dringend zu einer sauberen Architektur statt zu diesem Schnellschuss.
Die moderne Lösung: ViewModel und LiveData als Lebensretter
Die offizielle Android-Developer-Dokumentation empfiehlt einen klaren Weg: ViewModels in Kombination mit LiveData oder StateFlow. Ein ViewModel ist eine Komponente, die UI-bezogene Daten über den Lebenszyklus einer Activity hinweg verwaltet. Wenn die Activity beim Drehen zerstört wird, bleibt die ViewModel-Instanz im Speicher, bis die neue Activity-Instanz sie wieder anfordert. Der Trick liegt im ViewModelStoreOwner: Das System verknüpft das ViewModel mit einem Scope, der länger lebt als die einzelne Activity. Erst wenn die Activity endgültig geschlossen wird, wird onCleared() aufgerufen.
Die Vorteile dieser Architektur liegen auf der Hand:
- Datenüberleben: Das ViewModel übersteht Konfigurationsänderungen unbeschadet
- Keine Memory Leaks: Das ViewModel hält keine direkten Referenzen auf UI-Elemente
- Saubere Trennung: UI-Logik und Datenhaltung sind klar voneinander getrennt
- Testbarkeit: ViewModels lassen sich isoliert und ohne Android-Abhängigkeiten testen
LiveData ist das perfekte Gegenstück. Es handelt sich um einen beobachtbaren Datenhalter, der den Lebenszyklus der Observer respektiert. Wenn die Activity im zerstörten Zustand ist, sendet LiveData keine Updates, wodurch Abstürze vermieden werden. Sobald die neue Activity wieder aktiv ist, bekommt sie sofort den aktuellsten Datensatz geliefert. Der Code wird dadurch nicht nur sicherer, sondern auch viel lesbarer als das alte Callback-Chaos mit AsyncTask. Für Kotlin-Projekte empfehlen wir zusätzlich einen Blick auf StateFlow, das ähnliche Konzepte mit Kotlin-Coroutines umsetzt und sich nahtlos in moderne Architekturen einfügt.
Persistenz für den Ernstfall: onSaveInstanceState richtig nutzen
Ein ViewModel allein schützt uns nicht vor einem Prozess-Tod durch das System. Wenn der Akku des Google Pixel 8 im Café Central leer geht oder Android die App im Hintergrund beendet, ist auch das ViewModel weg. Für diesen Ernstfall müssen wir den transienten UI-Zustand über onSaveInstanceState in einem Bundle sichern. Die goldene Regel lautet: Speichert nur den minimalen UI-Zustand, der nötig ist, um die Ansicht wiederherzustellen. Ideal sind primitive Datentypen oder kleine, serialisierbare Objekte. Ein komplexes Bitmap-Bild oder eine riesige Liste haben im Bundle nichts verloren – dafür nutzen wir Room oder die Dateiablage.
Die wichtigsten Punkte für den Bundle-Einsatz im Überblick:
- Nur UI-Zustand sichern: Text in Eingabefeldern, ausgewählte Tabs, Scroll-Positionen
- Keine großen Datenmengen: Das Bundle hat eine Größenbeschränkung von etwa 500 KB
- Primitive Typen bevorzugen: Int, String, Boolean sind unproblematisch
- Komplexe Objekte serialisieren: Parcelable oder Serializable für eigene Datenklassen
Die modernste Methode ist das SavedStateHandle im ViewModel. Es vereint das Beste aus beiden Welten: Die Daten überleben Konfigurationsänderungen im ViewModel und werden zusätzlich im Bundle gesichert, falls der Prozess stirbt. Wir müssen uns nicht mehr manuell um onSaveInstanceState in der Activity kümmern, sondern kapseln die Logik sauber im ViewModel. Das hält den Code schlank und testbar.
Unser Praxistipp: Testen mit dem ‘Don’t keep activities’-Schalter
Die eleganteste Architektur nützt nichts, wenn sie nicht unter realen Bedingungen getestet wurde. Viele Bugs tauchen erst auf, wenn die App unter Stress gerät – etwa wenn ein Fahrgast in der Wiener U-Bahn schnell zwischen Apps wechselt und das System aggressiv Speicher freigibt. Aktiviert auf eurem Testgerät die Entwickleroptionen und schaltet die Einstellung “Keine Aktivitäten behalten” ein. Diese Option zerstört jede Activity sofort, sobald der User sie verlässt. Dreht das Gerät wild hin und her – wenn die App dabei abstürzt oder den Zustand verliert, habt ihr ein Problem, das früher oder später auch einen Nutzer im Stephansdom treffen wird.
Für die langfristige Qualitätssicherung empfehlen wir, das Drehen in instrumentierten Tests mit Espresso zu automatisieren. Ein einfacher Test, der ein Formular ausfüllt, das Gerät rotiert und dann den Inhalt verifiziert, ist Gold wert und schützt vor peinlichen Regressionen. Ergänzend dazu solltet ihr folgende Testszenarien durchspielen:
- Mehrfache Rotation: Dreht das Gerät mehrmals hintereinander und prüft die Stabilität
- Hintergrund-Wechsel: Schickt die App in den Hintergrund und öffnet mehrere andere Apps, bevor ihr zurückkehrt
- Netzwerkunterbrechung: Simuliert einen Netzwerkverlust während laufender API-Calls
- Speicherdruck: Testet auf Geräten mit wenig RAM, um Prozess-Tod-Szenarien zu provozieren
Fazit: So bleibt eure Android-App stabil bei jedem Dreh
Eine robuste App-Architektur ist kein Hexenwerk, sondern eine Frage der richtigen Werkzeuge. Wir setzen auf ViewModels für die Lebensdauer der Daten, nutzen LiveData für eine sichere Bindung an die UI und sichern kritischen Zustand mit SavedStateHandle für den Systemtod ab. Der faule Workaround über android:configChanges sollte der Vergangenheit angehören. Die anfängliche Investition in diese Architektur zahlt sich durch glückliche Nutzer in ganz Österreich sofort aus – denn nichts ist wertvoller als eine App, die einfach funktioniert, egal was das Gerät macht.
Häufig gestellte Fragen
Warum verliert meine ListView nach einer Drehung die Scroll-Position?
Die ListView und ihr Adapter sind Teil der Activity, die bei einer Konfigurationsänderung zerstört wird. Ohne ein ViewModel, das die Daten hält, muss der Adapter neu befüllt werden und die Scroll-Position wird auf null zurückgesetzt. Verwendet ein ViewModel mit LiveData, um die Liste und den Scroll-Zustand über die Zerstörung hinweg zu erhalten.
Kann ich android:configChanges nicht einfach für alle Activities setzen?
Davon raten wir dringend ab. Ihr umgeht damit den Mechanismus, den Android für die Ressourcenanpassung vorsieht. Spätestens wenn das System die App aus Speichergründen beendet, sind alle nicht persistierten Daten trotzdem weg und ihr habt euch das Leben mit manuellen Anpassungen nur schwerer gemacht.
Was ist der Unterschied zwischen onSaveInstanceState und SavedStateHandle?
onSaveInstanceState ist eine Callback-Methode in der Activity, in der ihr manuell Daten in ein Bundle schreibt. SavedStateHandle hingegen ist ein Bestandteil des ViewModels und wird automatisch mit dem Bundle synchronisiert. Es ermöglicht eine sauberere Kapselung der Logik im ViewModel.
Überlebt ein ViewModel, wenn ich die App im Hintergrund schließe?
Nein, ein ViewModel überlebt nur Konfigurationsänderungen der Activity. Wenn der Nutzer die App über die Übersicht wegwischt oder das System den Prozess beendet, wird das ViewModel zerstört. Für diesen Fall müsst ihr entweder das SavedStateHandle verwenden oder die Daten persistent in einer Datenbank speichern.
Leave a Reply