Programmier Tutorial - Apps für Android entwickeln - Activity und Fragment Lifecycle in Android

Programmier Tutorial – Teil 15: Der Activity und Fragment Lifecycle in Android


Activities und Fragmente durchschreiten während ihres Lebens verschiedene Stufen, die zusammen den jeweiligen Lebenszyklus bilden. In Android wird dies als Activity Lifecycle und Fragment Lifecycle bezeichnet.

In dieser Lektion möchten wir den jeweiligen Lifecycle näher betrachten und auf die Besonderheiten eingehen. Das Thema ist sehr komplex, daher können wir nicht auf jedes Detail ausführlich eingehen.

Dennoch ist das umfassende Verständnis des Android Lifecycle für App-Entwickler sehr wichtig. Daher werden wir in den einzelnen Kapiteln immer wieder auf die Android Developer Webseite verweisen. Die dortigen Erklärungen sind sehr detailliert und hervorragend aufbereitet.

Nun zum Aufbau dieser Lektion. Wir werden zunächst mit dem Activity Lifecycle von Android beginnen und anschließend unsere App um die verschiedenen Lifecycle-Callbacks erweitern.

Dadurch können wir uns die einzelnen Lebensphasen unserer Activity direkt im Android-Log anzeigen lassen und somit die internen Vorgänge besser verstehen.

Nachdem wir den Activity Lifecycle kennengelernt haben, betrachten wir den Fragment Lifecycle von Android. Dieser unterscheidet sich leicht vom Vorherigen und verfügt über einige zusätzliche Lifecycle-Callbacks. Um die internen Vorgänge besser nachvollziehen zu können, werden wir auch ein Fragment unserer App mit den verfügbaren Lifecycle-Callbacks erweitern.

Danach betrachten wir, wie in Android der Zustand unserer App gespeichert wird. Da wir den Lebenszyklus von Activities und Fragmenten nun kennengelernt haben, können wir besser verstehen in welcher Lebensphase dies geschieht und warum. Außerdem zeigen wir, wie der gespeicherte Zustand beim Neuerstellen der Activity bzw. des Fragments wieder hergestellt wird.

Im letzten Abschnitt testen wir unsere Android App und besprechen die neue Funktion, das Speichern des aktuellen Zustands unserer App und das anschließende Laden dieses Zustands.

Nun wünschen wir euch viel Spaß bei dem fünfzehnten Teil unseres großen Android Tutorials. Los geht’s!

1. Der Activity Lifecycle (Lebenszyklus) in Android

Android Apps Programmieren Online-Kurs

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

Eine Activity ist ein Bestandteil einer Anwendung, die einen Bildschirm zur Verfügung stellt, mit dem der Benutzer interagieren kann.

Jeder Activity wird ein Fenster (window) zugewiesen, in dem sie ihre Benutzeroberfläche (user interface) zeichnen bzw. erstellen kann.

Eine Android Anwendung besteht meist aus mehreren Activities, die lose miteinander verbunden sind. Jede Activity kann eine andere Activity starten, dabei wird die aktuelle Activity gestoppt und die gestartete Activity kommt in den Fokus.

Der Zustand der gestoppten Aktivität bleibt im Android System erhalten, da die gestoppte Activity bereits auf dem Back Stack abgelegt war. Die gestartete Activity wird auch auf dem Back Stack abgelegt und erhält als einzige Activity den Fokus.

Drückt der Benutzer jetzt den Back-Button, wird die gestartete Aktivität vom Back Stack entfernt und zerstört. Zeitgleich wird die vorherige, gestoppte Aktivität wieder fortgesetzt (restarted) und erhält den Fokus.

Der Back Stack arbeitet nach dem Last in, First out-Prinzip. Detaillierte Informationen über den Back Stack könnt ihr hier finden.


Wenn eine Activity gestoppt wird, weil eine neue Activity startet, wird sie über die Zustandsänderung durch die Lifecycle-Callbacks informiert. In Android sind mehrere Callback-Methoden implementiert, die bei einer Zustandsänderung der Aktivität aufgerufen werden. Jede Callback-Methode bietet die Gelegenheit bestimmte, notwendige Arbeiten auszuführen, bevor die Zustandsänderung in Kraft tritt.

Wir werden uns im folgenden Abschnitt den Activity Lifecycle von Android genauer ansehen.

1.1 Die drei Zustände in denen eine Activity existieren kann

Durch Implementieren der Lifecycle-Callbacks kann der Lebenszyklus der eigenen Activities verwaltet werden. Die Callbacks richtig zu implementieren ist entscheidend für das Entwickeln komplexer und flexibler Android Anwendungen. Der Lifecycle einer Activity wird von anderen Activities, dem eigenen Task und dem Back Stack direkt beeinflusst.

Eine Activity kann in den folgenden drei Zuständen existieren:

  • Resumed – Die Activity ist im Bildschirmvordergrund und besitzt den Benutzer-Fokus.
  • Paused – Eine andere Activity ist im Bildschirmvordergrund und besitzt den Benutzer-Fokus, verdeckt jedoch die überlagerte Activity nicht komplett. Ein Settings-Dialog bspw. verdeckt die Activity nur teilweise. Im Paused-Zustand ist eine Activity vollständig intakt (alive). Das Activity-Objekt wird im Speicher gehalten und alle Zustands- und Memberinformationen bleiben erhalten. Die pausierte Activity kann jedoch vom Android System in Situationen mit extrem niedrigem Speicher zerstört (killed) werden.
  • Stopped – Die Activity wird von einer anderen Activity vollständig verdeckt und befindet sich nun um Bildschirmhintergrund. Auch eine gestoppte Activity ist intakt. Das Activity-Objekt wird im Speicher gehalten und alle Zustands- und Memberinformationen bleiben erhalten. Die Activity ist jedoch nicht mehr dem Window Manager zugewiesen. In Situationen mit extrem wenig verfügbarem Speicher kann eine gestoppte Activity vom Android System zerstört werden.

Somit bleibt festzuhalten, dass eine pausierte oder gestoppte Activity jederzeit vom Android System zerstört werden kann. Dies geschieht entweder durch Aufrufen der finish()-Methode oder durch direktes Beenden ihres Prozesses. Wird eine zerstörte Activity wieder geöffnet, muss sie von ganz vorne wieder neu erstellt werden.

1.2 Die Lifecycle-Callbacks einer Activity in Android

Wenn eine Activity einen Zustand verlässt und einen neuen Zustand betritt, wird sie über verschiedene Callback-Methoden über die Zustandsänderung informiert. Alle Callback-Methoden können überschrieben werden.

Somit kann auf die Zustandsänderung entsprechend reagiert und die jeweils notwendige Arbeit ausgeführt werden. Um zu wissen welche Arbeit in welchem Callback durchgeführt werden sollte, ist eine genaue Kenntnis des Lifecycle-Callbacks erforderlich.

In Android sind die folgenden Lifecycle-Callbacks definiert:

  • onCreate(Bundle savedInstanceState) – Die Activity wird erstellt. Hier beginnt der Lebenszyklus.
  • onStart() – Die Activity ist kurz davor sichtbar zu werden.
  • onResume() – Die Activity ist sichtbar geworden und hat den Benutzer-Fokus.
  • onPause() – Eine andere Activity erhält den Fokus. Diese Activity wird pausiert.
  • onStop() – Die Activity ist nicht mehr sichtbar und wird nun gestoppt.
  • onRestart() – Die Activity wird neu gestartet. Als Nächstes wird onStart() aufgerufen.
  • onDestroy() – Die Activity wird zerstört werden. Hier endet der Lebenszyklus.
  • onSaveInstanceState(Bundle outState) – Die Activity wird in den nächsten Momenten gestoppt. Jetzt kann der aktuelle Zustand als Sammlung von Key-Value Paaren gespeichert werden.
  • onRestoreInstanceState(Bundle savedInstanceState) – Die Activity wird wieder hergestellt, nachdem sie aufgrund von Speichermangel zerstört wurde. Dieser Callback wird nur aufgerufen, wenn das Bundle nicht null ist. Der Aufruf erfolgt unmittelbar nach onStart().

Die folgende Abbildung verdeutlicht in welcher Reihenfolge die Lifecycle-Callbacks aufgerufen werden:

android activity lifecycle

Die Callback-Methoden des Activity Lifecycle in Android

Die Rechtecke repräsentieren die Callback-Methoden, die von euch implementiert werden können, um notwendige Operationen bei Zustandsänderungen durchzuführen.

Die beiden Callback-Methoden onSaveInstanceState und onRestoreInstanceState sind nicht in der Grafik eingetragen, da sie nicht in jedem Fall aufgerufen werden. Wir werden in dem Abschnitt über das Speichern und Laden des Activity-Zustands näher auf sie eingehen.

Aufpassen!
Wird eine dieser Lifecycle-Methoden implementiert, muss unbedingt als Erstes die Implementierung der Basisklasse aufgerufen werden, bevor der eigene Code beginnt.


protected void onPause() {
    super.onPause(); // Aufrufen der Implementierung der Superklasse

    // Hier folgt der eigene Code
}

Alle Callback-Methoden zusammengenommen ergeben den kompletten Lebenszyklus einer Activity in Android. Der Zyklus kann mehrfach durchlaufen werden.

Betrachtet man den Lebenszyklus im Detail, können die folgenden drei verschachtelten Schleifen erkannt werden:

  • Die Gesamte Lebenszeit einer Aktivität findet zwischen dem Aufruf der onCreate() und onDestroy() Methoden statt. Eine Activity sollte die grundlegenden Initialisierungen in der onCreate() Methode vornehmen und alle verbleibenden Ressourcen in der onDestroy() Methode wieder freigeben.
  • Die Sichtbare Lebenszeit einer Activity findet zwischen dem Aufruf der onStart() und onStop() Methoden statt. Während dieser Zeit kann der Benutzer die Activity auf dem Bildschirm sehen und mit ihr interagieren. Sie kann aber zeitweise von einer anderen Activity zum Teil verdeckt sein, ist dann aber noch sichtbar im Hintergrund.
  • Die Vordergrund Lebenszeit einer Activity in Android findet zwischen dem Aufruf der onResume() und onPause() Methoden statt. Während dieser Zeit befindet sich die Activity im Bildschirmvordergrund über allen anderen Activities und besitzt den Benutzer-Fokus. Eine Activity kann sehr oft in diesen Zustand gehen und diesen wieder verlassen. Bspw. wird die onPause() Methode aufgerufen, wenn das Android Gerät in den Schlafmodus geht oder wenn ein Dialog erscheint. Da diese Zustandsänderung oft stattfindet, sollte der Code in diesen beiden Methoden sehr schnell ausführbar sein (lightweight), um langsame Übergänge zu vermeiden.

1.3 Die Lifecycle-Callbacks einer Activity im Detail

In der unteren Aufstellung ist jede Callback-Methode nochmals detailliert beschrieben. Zusätzlich beschreiben wir welche Methode im Anschluss aufgerufen wird und ob der Prozess der App vom Android System beendet werden kann, nachdem die Callback-Methode abgearbeitet worden ist.

onCreate()
Wird aufgerufen, wenn die Activity das erste Mal erzeugt wird. In ihr sollten alle Initialisierungsarbeiten vorgenommen werden, wie Erzeugen von Views oder Daten für Listen registrieren. Außerdem wird dieser Methode ein Bundle-Objekt übergeben, welches den vorherigen Zustand der Activity enthält, falls dieser Zustand festgehalten wurde. An dieser Stelle kann das Android System den Prozess der App nicht beenden (kill process). Dieser Methode folgt immer die onStart() Methode.


onRestart()
Wird aufgerufen, nachdem die Activity gestoppt wurde, kurz bevor sie erneut gestartet wird. Auch nach dieser Methode kann das Android System den App-Prozess nicht beenden. Der Methode folgt immer die onStart() Methode.


onStart()
Wird aufgerufen, kurz bevor die Activity für den Benutzer sichtbar wird. Auch nach dieser Methode kann das Android System den App-Prozess nicht beenden. Der Methode folgt die onResume() Methode, wenn die Activity in den Vordergrund rückt bzw. die onStop() Methode, wenn die Activity verdeckt wird.


onResume()
Wird aufgerufen, kurz bevor die Activity mit dem Benutzer interagiert. Zu diesem Zeitpunkt ist die Activity ganz oben auf dem Activity Stack und empfängt die Benutzereingaben. Auch nach dieser Methode kann das Android System den App-Prozess nicht beenden. Der Methode folgt immer die onPause() Methode.


onPause()
Wird aufgerufen, kurz bevor das System eine andere Activity startet. In ihr sollten vorgenommene Änderungen gespeichert und Animationen beendet werden. Diese Vorhaben sollten unbedingt sehr schnell ausführbar sein, da die nächste Activity erst danach fortgesetzt wird. Nach dieser Methode kann das Android System den App-Prozess direkt beenden, sollte dies aufgrund von Speichermangel erforderlich sein. Der Methode folgt die onResume() Methode, wenn die Activity wieder in den Vordergrund rückt bzw. die onStop() Methode, wenn die Activity verdeckt wird.


onStop()
Wird aufgerufen, wenn die Activity für den Benutzer nicht mehr sichtbar ist. Dies kann passieren, weil die Activity zerstört werden wird oder eine andere Activity gestartet wurde und diese vollständig überdeckt. Nach dieser Methode kann das Android System den App-Prozess direkt beenden, sollte dies aufgrund von Speichermangel erforderlich sein. Der Methode folgt die onRestart() Methode, wenn die Activity wieder fortgesetzt werden soll bzw. die onDestroy() Methode, wenn die Activity endgültig geschlossen werden soll.


onDestroy()
Wird aufgerufen, kurz bevor die Activity zerstört wird. Dies ist der letzte Aufruf den eine Activity erhält. Entweder wird die Activity gerade planmäßig mit der Methode finish() beendet oder das Android System zerstört die Activity temporär, um Speicher freizugeben. Mit der Methode isFinishing() kann überprüft werden, welches von beiden der Fall ist. Dieser Methode folgt kein weiterer Aufruf einer Callback-Methode.


Wie den Beschreibungen der Callback-Methoden zu entnehmen ist, kann der Prozess in dem eine Activity ausgeführt wird nur nach den drei Methoden onPause(), onStop() und onDestroy() beendet werden (process killed).

Somit kann eine Activity im schlimmsten Fall direkt nach der onPause() Methode geschlossen werden. Sie ist die letzte Methode, die garantiert aufgerufen wird, bevor das System den Prozess direkt aufgrund von Speichermangel beendet. Die Methoden onStop() und onDestroy() werden in diesem Fall nicht mehr aufgerufen.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

Daher sollten in der Methode onPause() die kritischen Daten persistent gespeichert werden. Dies sollte aber unbedingt schnell ausgeführt werden, da erst nach diesen Anweisungen der Übergang in die nächste Activity erfolgen kann und ein Blockieren die Benutzererfahrung stark verschlechtern würde.

Nach den anderen Callback-Methoden kann die Activity nicht vom Android System beendet werden. Somit kann eine Activity direkt beendet werden, von dem Zeitpunkt ab dem onPause() verlassen wird bis zu dem Moment an dem onResume() aufgerufen wird. Danach kann die Activity erst wieder direkt vom System beendet werden, wenn die onPause() Methode abgearbeitet wurde.

Weitere Infos über den Lebenszyklus von Activities findet ihr hier.

2. Implementieren der Activity Lifecycle-Callbacks

Jetzt haben wir den Activity Lifecycle von Android mit seinen Callback-Methoden ausführlich in der Theorie kennengelernt.

Als Nächstes möchten wir das erlernte Wissen in die Praxis umsetzen.

Dazu erweitern wir die Klasse MainActivity unserer Android App um die Callback-Methoden. In den Callbacks geben wir eine kurze Log-Meldung aus, die uns über den Aufruf der jeweiligen Callback-Methode informiert.

2.1 Einfügen der Callback-Methoden in die MainActivity-Klasse

Wir öffnen dazu in Android Studio die Datei MainActivity.java und fügen den unteren Quellcode in die Klasse MainActivity ein:

MainActivity.java

private final String LOG_TAG = MainActivity.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Log.v(LOG_TAG, "In Callback-Methode: onCreate()");
}

@Override
protected void onStart() {
    super.onStart();
    Log.v(LOG_TAG, "In Callback-Methode: onStart()");
}

@Override
protected void onResume() {
    super.onResume();
    Log.v(LOG_TAG, "In Callback-Methode: onResume()");
}

@Override
protected void onPause() {
    super.onPause();
    Log.v(LOG_TAG, "In Callback-Methode: onPause()");
}

@Override
protected void onStop() {
    super.onStop();
    Log.v(LOG_TAG, "In Callback-Methode: onStop()");
}

@Override
protected void onRestart() {
    super.onRestart();
    Log.v(LOG_TAG, "In Callback-Methode: onRestart()");
}

@Override
protected void onDestroy() {
    super.onDestroy();
    Log.v(LOG_TAG, "In Callback-Methode: onDestroy()");
}

Da wir für das Ausgeben der Meldungen die Klasse Log verwenden, muss diese noch mit der folgenden Import-Anweisung am Anfang des Quellcodes importiert werden:

import android.util.Log;

Unsere MainActivity-Klasse sollte in Android Studio jetzt folgendermaßen aussehen:

android activity lifecycle callbacks

Implementierung der Lifecycle-Callbacks einer Activity in Android

Die Import-Anweisung der Log-Klasse ist mit Blau unterstrichen (Markierung A). Der weiter oben vorgestellte Code-Block ist bereits in die MainActivity-Klasse eingefügt und mit einem blauen Rahmen umschlossen (Markierung B).

Da wir an einigen Stellen Veränderungen in der Datei MainActivity.java vorgenommen haben, kann es leicht zu Missverständnissen kommen. Daher solltet ihr noch einmal in Ruhe den kompletten Quellcode der Klasse anschauen und mit eurem vergleichen:

MainActivity.java (als ZIP-Datei gepackt)

Als Nächstes starten wir unsere Android Anwendung wie in Abschnitt 8 beschrieben.

2.2 Testen der Activity Lifecycle Callbacks mit unserer App

Nun möchten wir die Log-Meldungen unserer App in Echtzeit verfolgen. Unsere App sollte bereits über Android Studio gestartet worden sein und Log-Meldungen über die ADB erhalten. Wie man in Android Studio Meldungen loggt, könnt ihr in diesem Beitrag nachlesen: Logging in Android Studio.

Es ist wichtig, dass in dem Android-Tab in der logcat-Ansicht im Suchfeld der Name der Klasse angegeben ist. In diesem Fall muss MainActivity in das Suchfeld (Markierung 1 in der unteren Abbildung) eingegeben werden, um nach unseren Log-Meldungen zu filtern.

Kurz nachdem unsere App gestartet wurde, bekommen wir folgende Log-Meldungen in Android Studio angezeigt:

android activity lifecycle log

Unsere Finanz-App wurde gestartet. Die MainActivity besitzt den Benutzer-Fokus.

Wie anhand der Abbildung aus Abschnitt 1.2 zu erwarten ist, wird zuerst die Callback-Methode onCreate() ausgeführt. Ihr folgen die Methodenaufrufe der Callbacks onStart() und onResume(). Nach Beenden der onResume() Methode besitzt die MainActivity den Benutzer-Fokus und ist vollständig im Bildschirmvordergrund.

Als Nächstes starten wir eine weitere Activity über das Overflow Menu. Mit einem Klick auf den Settings-Menüeintrag starten wir die Einstellungen-Activity. Als Ergebnis erhalten wir folgende Log-Meldungen in Android Studio angezeigt:

android activity lifecycle log

Eine neue Activity wird gestartet. Sie übernimmt den Benutzer-Fokus und überdeckt die MainActivity vollständig.

Da die MainActivity jetzt überlagert wird, wurde vorher die onPause() Methode aufgerufen und anschließend die onStop() Methode, da die gestartete Activity die MainActivity vollständig überdeckt.

Mit einem Klick auf den Back-Button gelangen wir zurück in die MainActivity und können folgende Log-Meldungen in Android Studio lesen:

android activity lifecycle log

Wieder zurück in der MainActivity. Nach der onRestart() Methode wird sofort onStart() aufgerufen.

Die Einstellungen-Activity wird beendet und die MainActivity rückt wieder zurück in den Bildschirmvordergrund. Dadurch wird der Restart-Pfad durchlaufen. Es wird zuerst die onRestart() Methode aufgerufen, der umgehend die onStart() Methode folgt. Anschließend wird die onResume() Methode ausgeführt und die MainActivity erhält den Benutzer-Fokus zurück.

Als letzten Schritt möchten wir unsere Android App schließen. Mit einem Klick auf den Back-Button verlassen wir unsere App, worauf sich folgende Log-Meldung ergibt:

android activity lifecycle log

Die App wird mit dem Back-Button geschlossen. Das Android System beendet die MainActivity endgültig.

Die Activity wird, durch das Verlassen der App mittels Back-Button, nun vom Android System vollständig und endgültig zerstört. Dabei wird zuerst die onPause() Methode aufgerufen, danach die onStop() Methode und schließlich die onDestroy() Methode.

Die MainActivity ist jetzt endgültig zerstört worden. Es wurde vom Android System nicht versucht den Zustand der Activity zu speichern, da Android davon ausgeht, dass der Benutzer mit der Activity fertig ist und nicht erwartet, dass der Activity-Zustand gespeichert wird. Dies ist so in dem Android System konzipiert und sollte von Android-Entwicklern gut verstanden werden.

Da wir nun den Activity Lifecycle von Android ausführlich betrachtet haben und diesen auch in der Praxis testen konnten, sind wir jetzt bereit den nächsten Schritt zu gehen und den Fragment Lifecycle von Android kennenzulernen.

3. Der Fragment Lifecycle (Lebenszyklus) in Android

Ein Fragment ist ein modularer Bereich einer Activity, der über seinen eigenen Lebenszyklus verfügt. Ein Fragment erhält seine eigenen Callbacks und kann in mehreren Activities wiederverwendet werden. Es können auch mehrere Fragmente in einer Activity eingebettet sein.

Der Fragment Lifecycle wird direkt von dem Activity Lifecycle, der übergeordneten Activity, beeinflusst.

Wird bspw. die Activity pausiert, dann werden auch alle eingebetteten Fragmente pausiert. Wird die Activity zerstört, werden auch all ihre Fragmente zerstört.

Solange sich eine Activity in dem Resumed-Zustand befindet, können ihre untergeordneten Fragmente beliebig manipuliert werden. Fragmente können je nach Bedarf hinzugefügt und auch gelöscht werden. Diese Fragment Transactions können dem, von der Activity verwalteten, Back Stack hinzugefügt werden. Jeder Back Stack-Eintrag hält die ausgeführte Fragment Verschiebung (Transaktion) fest. Der Back Stack erlaubt den Nutzern die Fragment Transaktionen wieder rückgängig zu machen, durch Pressen der Back-Taste.

Wird ein Fragment einem Activity Layout hinzugefügt, „lebt“ es in einer ViewGroup innerhalb der View-Hierarchie der Activity. Fragmente können aber auch ohne Benutzeroberfläche (UI) als unsichtbare Arbeiter implementiert werden, dann sind sie kein Teil des Activity Layouts.

3.1 Die Lifecycle-Callbacks eines Fragments in Android

Der Quellcode eines Fragments ist dem einer Activity sehr ähnlich. Ein Fragment besitzt die gleichen Callback-Methoden wie eine Activity (bspw. onCreate(), onStart(), onPause(), onStop() und onDestroy()).

Zusätzlich verfügt ein Fragment über einige weitere Callback-Methoden, wie onAttach(), onCreateView(), onActivityCreated(), onDestroyView() und onDetach().

Da es so viele Gemeinsamkeiten zwischen Activities und Fragmenten gibt, kann in vielen Fällen Code von der Callback-Methode der Activity in die entsprechende Callback-Methode des Fragments umgelagert werden, wenn eine Android App auf Fragmente umgestellt werden soll.

Die folgenden drei Lifecycle Callbacks sollten bei jedem Fragment implementiert werden:

onCreate()
Das Android System ruft diese Methode auf, wenn das Fragment erzeugt wird. In ihr sollte die Initialisierung wichtiger Komponenten des Fragments, die beibehalten werden sollen, wenn das Fragment pausiert oder gestoppt wird, erfolgen.


onCreateView()
Das Android System ruft diese Methode auf, wenn das Fragment seine Benutzeroberfläche zum ersten Mal zeichnet. Um eine Benutzeroberfläche für das Fragment zu erstellen, muss diese Methode ein View-Objekt, welches die Basis des Fragment Layouts enthält, zurückgeben. Man kann null zurückgeben, wenn das Fragment über keine Benutzeroberfläche verfügen soll.


onPause()
Das Android System ruft diese Methode auf, wenn es erste Anzeichen gibt, dass der Benutzer das Fragment verlassen wird. In ihr sollten die vorgenommenen Änderungen persistent gespeichert werden, da das Fragment ab diesem Zeitpunkt direkt zerstört werden kann.


android fragment lifecycle

Die Callback-Methoden des Fragment Lifecycles in Android

In der Abbildung rechts sind die Lifecycle-Callbacks eines Fragments in Android dargestellt.

Wie eine Activity, kann auch ein Fragment in drei verschiedenen Zuständen existieren, diese sind:

  • Resumed – Das Fragment ist sichtbar und befindet sich in der gerade ausgeführten Activity.
  • Paused – Eine andere Activity ist im Bildschirmvordergrund und besitzt den Benutzer-Fokus. Die Activity in der sich das Fragment befindet, wird aber noch teilweise angezeigt.
  • Stopped – Das Fragment ist nicht sichtbar. Entweder wurde die übergeordnete Activity gestoppt oder das Fragment aus der Activity entfernt und dem Back Stack hinzugefügt. Ein gestopptes Fragment ist immer noch existent, alle Zustands- und Memberinformationen werden vom Android System beibehalten. Für den Benutzer ist das Fragment jedoch nicht mehr zu sehen und wird zerstört werden, sobald die Activity zerstört wird.

Wie bei einer Activity kann auch der Zustand eines Fragments mit Hilfe eines Bundles gespeichert werden.

Sollte der Prozess der Activity vom System direkt beendet worden sein, kann mit diesem Bundle der Zustand des Fragments wiederhergestellt werden, wenn die Activity wieder erstellt wird.

Der Fragment-Zustand kann mit der Callback-Methode onSaveInstanceState abgespeichert werden.

In den Callbacks onCreate(), onCreateView() und onActivityCreated() kann der Zustand dann wieder hergestellt werden. Auf dieses Thema gehen wir in einem späteren Abschnitt genauer ein.

Der Hauptunterschied im Lebenszyklus von Activities und Fragmenten ist die Art und Weise wie sie jeweils in dem entsprechenden Back Stack angelegt werden.

Eine gestoppte Activity wird auf den Activities Back Stack gelegt, welcher vom Android System verwaltet wird. Der User kann mit dem Back-Button zurück in die gestoppte Activity navigieren.

Ein Fragment wird jedoch auf einen speziellen Back Stack gelegt, welcher von der übergeordneten Activity verwaltet wird. Dies ist aber nur der Fall, wenn es mit dem Aufruf von addToBackStack() beim Entfernen des Fragments explizit so angewiesen wurde.

3.2 Auswirkung des Activity Lifecycle auf den Fragment Lifecycle

Der Lebenszyklus einer Activity, in der ein Fragment existiert, beeinflusst direkt den Lebenszyklus dieses Fragments. Aus jedem Aufruf eines Lifecycle-Callbacks in der Activity resultiert ein entsprechender Aufruf für jedes untergeordnete Fragment. Wird bspw. onPause() in der Activity aufgerufen, erfolgt umgehend der Aufruf der onPause() Callback-Methode jedes untergeordneten Fragments.

Fragmente besitzen einige zusätzliche Lifecycle-Callbacks gegenüber Activities. Diese führen bestimmte Interaktionen mit der Activity aus, wie das Erzeugen und Zerstören der Benutzeroberfläche der Fragmente.

Die zusätzlichen Callback-Methoden eines Fragments in Android sind:

  • onAttach() – Wird aufgerufen, wenn das Fragment mit der Activity verbunden wurde.
  • onCreateView() – Wird aufgerufen, um die View-Hierarchie des Fragments zu erstellen.
  • onActivityCreated() – Wird aufgerufen, wenn die onCreate() Methode der Activity abgeschlossen ist.
  • onDestroyView() – Wird aufgerufen, um die View-Hierarchie des Fragments zu entfernen.
  • onDetach() – Wird aufgerufen, wenn die Verbindung zwischen Fragment und Activity gelöst wird.

Sobald eine Activity den Resumed-Zustand betreten hat, können ihr beliebig Fragmente hinzugefügt und entfernt werden. Nur in diesem Zustand der Activity kann sich der Lebenszyklus-Zustand ihrer Fragmente unabhängig von ihrem eigenen ändern.

Sobald die Activity den Resumed-Zustand verlässt, werden ihre untergeordneten Fragmente von der Activity durch ihre Lebenszyklen geführt.

Auf der Developer Webseite von Android könnt ihr weitere Informationen über Fragmente und ihren Lebenszyklus finden.

4. Implementieren der Fragment Lifecycle-Callbacks

Jetzt haben wir den Fragment Lifecycle von Android mit seinen Callback-Methoden ausführlich betrachtet. Als Nächstes möchten wir überprüfen, wie sich die Lifecycle-Callbacks in unserer Android App auswirken.

Dazu erweitern wir die Klasse AktienlisteFragment unserer Android App um die im vorherigen Abschnitt vorgestellten Fragment Callback-Methoden.

In den Lifecycle-Callbacks geben wir eine kurze Log-Meldung aus, die uns über den Aufruf der jeweiligen Callback-Methode informiert.

Somit können wir im Logging-Tab von Android Studio direkt nachvollziehen, in welchem Lifecycle-Zustand das Fragment sich gerade befindet.

4.1 Einfügen der Callbacks in die AktienlisteFragment-Klasse

Wir öffnen dazu in Android Studio die Datei AktienlisteFragment.java und fügen den unteren Quellcode in die Klasse AktienlisteFragment ein:

AktienlisteFragment.java

// Tag für das Logging des Fragment-Lifecycle definieren
private final String LOG_TAG = AktienlisteFragment.class.getSimpleName();

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.v(LOG_TAG, "In Callback-Methode: onAttach()");
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    Log.v(LOG_TAG, "In Callback-Methode: onActivityCreated()");
}

@Override
public void onStart() {
    super.onStart();
    Log.v(LOG_TAG, "In Callback-Methode: onStart()");
}

@Override
public void onResume() {
    super.onResume();
    Log.v(LOG_TAG, "In Callback-Methode: onResume()");
}

@Override
public void onPause() {
    super.onPause();
    Log.v(LOG_TAG, "In Callback-Methode: onPause()");
}

@Override
public void onStop() {
    super.onStop();
    Log.v(LOG_TAG, "In Callback-Methode: onStop()");
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    Log.v(LOG_TAG, "In Callback-Methode: onDestroyView()");
}

@Override
public void onDestroy() {
    super.onDestroy();
    Log.v(LOG_TAG, "In Callback-Methode: onDestroy()");
}

@Override
public void onDetach() {
    super.onDetach();
    Log.v(LOG_TAG, "In Callback-Methode: onDetach()");
}

Zusätzlich muss jeweils eine entsprechende Log-Anweisung in die Callback-Methoden onCreate() und onCreateView() eingefügt werden. Insgesamt sind damit 11 Callback-Methoden in der AktienlisteFragment-Klasse implementiert.

Weiterhin muss folgende Import-Anweisung am Anfang des Quellcodes eingefügt werden:

import android.app.Activity;

Da wir an einigen Stellen Veränderungen in der Datei AktienlisteFragment.java vorgenommen haben, kann es leicht zu Missverständnissen kommen. Daher solltet ihr noch einmal in Ruhe den kompletten Quellcode der Klasse anschauen und mit eurem vergleichen:

AktienlisteFragment.java (als ZIP-Datei gepackt)

Als Nächstes starten wir unsere Android Anwendung wie in Abschnitt 8 beschrieben.

4.2 Testen der Fragment Lifecycle Callbacks mit unserer App

Nun möchten wir die Log-Meldungen unserer App in Echtzeit verfolgen. Unsere App sollte bereits über Android Studio gestartet worden sein und Log-Meldungen über die ADB erhalten. Wie man in Android Studio Meldungen loggt, könnt ihr in diesem Beitrag nachlesen: Logging in Android Studio.

Es ist wichtig, dass in dem Android-Tab in der logcat-Ansicht im Suchfeld der Name der Klasse angegeben ist. In diesem Fall muss AktienlisteFragment in das Suchfeld (Markierung 1 in der unteren Abbildung) eingegeben werden, um nach unseren Log-Meldungen zu filtern.

Kurz nachdem unsere App gestartet wurde, bekommen wir folgende Log-Meldungen in Android Studio angezeigt. [Zur besseren Lesbarkeit einfach auf die Abbildungen klicken]

android fragment lifecycle test

Die Finanz-App wurde gestartet. MainActivity und AktienlisteFragment sind im Resumed-Zustand.

Da unsere App bereits gestartet wurde, haben die MainActivity und das AktienlisteFragment bereits den Resumed-Zustand erreicht. Das Fragment hat dazu die Callback-Methoden onAttach(), onCreate(), onCreateView(), onActivityCreated(), onStart() und onResume() durchlaufen.

In der oberen Abbildung sind außerdem die fünf Log-Meldungen aus Lektion 5 zu sehen. Diese Meldungen werden in der onCreateView() Methode ausgegeben.

Als Nächstes haben wir in unserer Android App über den Settings-Menüeintrag des Overflow Menu die EinstellungenActivity gestartet. Dadurch werden die MainActivity und das AktienlisteFragment vollständig überdeckt und wir erhalten folgende Log-Meldung:

android fragment lifecycle test

Eine neue Activity wurde gestartet und verdeckt das AktienlisteFragment. Es ist jetzt im Stopped-Zustand.

Da das AktienlisteFragment jetzt nicht mehr sichtbar ist, verlässt es den Resumed-Zustand und wechselt in den Stopped-Zustand. Dabei werden die Lifecycle Callbacks onPause() und onStop() aufgerufen.

Mit einem Klick auf den Back-Button verlassen wir wieder die EinstellungenActivity und kehren zur MainActivity zurück. Dadurch wird auch das AktienlisteFragment wieder sichtbar und wechselt in den Resumed-Zustand.

android fragment lifecycle test

Mit dem Back-Button wurde zurück zur MainActivity navigiert. Das AktienlisteFragment ist wieder sichtbar.

Bei dem Zustandswechsel werden die Callback-Methoden onStart() und onResume() aufgerufen.

Als letzten Schritt schließen wir die Anwendung mit einem Klick auf den Back-Button. Dies wird vom Android System so interpretiert, als wären wir mit der App fertig und möchten, dass diese komplett beendet wird. Daher werden die MainActivity und das AktienlisteFragment zerstört und der jeweilige Zustand wird nicht gespeichert.

android fragment lifecycle test

Die App wird mit dem Back-Button beendet. Die MainActivity und das AktienlisteFragment werden dabei zerstört.

Für das Zerstören des Fragments werden die Callbacks onPause(), onStop(), onDestroyView(), onDestroy() und onDetach() der Reihe nach aufgerufen. Damit ist das Fragment vollkommen zerstört und muss beim nächsten Starten unserer Anwendung wieder frisch erzeugt werden.

Jetzt haben wir den Lebenszyklus von Activities und Fragmenten näher kennengelernt. Wir wissen nach welchem Schema sie erstellt und auch wieder zerstört werden. Im nächsten Abschnitt werden wir erfahren, wie der letzte Zustand einer Activity bzw. eines Fragments konserviert wird, um ihn beim erneuten Erzeugen der Activity bzw. des Fragments wieder herstellen zu können.

5. Den Zustand von Activities und Fragmenten in Android speichern und laden

Wenn eine Activity bzw. ein Fragment pausiert oder gestoppt wird, dann bleibt der jeweilige Zustand erhalten. Das Activity- bzw. Fragment-Objekt wird dabei im Speicher gehalten. Alle Informationen über seine Member- und Zustands-Variablen werden beibehalten. Dadurch bleiben auch alle Änderungen erhalten, die der User in der Activity bzw. dem Fragment vorgenommen hat. Wird die Activity bzw. das Fragment wieder fortgesetzt, sind daher keine Änderungen verlorengegangen.

Wenn eine Activity bzw. ein Fragment jedoch vom Android System aufgrund von Speichermangel oder einer Konfigurationsänderung (z.B. Bildschirmrotation) zerstört wird, kann der letzte Zustand nicht einfach wiederhergestellt werden, da das entsprechende Objekt zerstört wurde.

Das jeweilige Objekt muss wieder neu erstellt werden, wenn der Benutzer zurück zur Activity bzw. zum Fragment navigiert. Damit die Informationen über die Member- und Zustands-Variablen beibehalten werden können, müssen sie daher vor dem Zerstören des Objekts mit Hilfe der Callback-Methode onSaveInstanceState() gespeichert werden.

Das Android System ruft die Methode onSaveInstanceState() auf, bevor die Activity bzw. das Fragment zerstört werden können. Beim Aufruf wird der Methode ein Bundle übergeben, in welches die Zustandsinformationen gespeichert werden können. Dies geschieht mit Hilfe von Schlüssel-Wert Paaren (key-value pairs), wo zu einem einzigartigen Schlüssel ein bestimmter Wert abgelegt wird.

Wenn eine vorher zerstörte Activity bzw. ein Fragment wieder neu erstellt wird, übergibt das Android System dieses Bundle an die passenden Lifecycle-Callbacks. Bei Activities sind das die Methoden onCreate() und onRestoreInstanceState(), bei Fragmenten die Methoden onCreate(), onCreateView() und onActivityCreated(). In jeder dieser Methoden kann das Wiederherstellen des Activity- bzw. Fragment-Zustands mit Hilfe des Bundles vorgenommen werden.

Wenn der Zustand vorher nicht gespeichert wurde oder kein vorheriger Zustand existierte, ist der Wert des übergebenen Bundles null. Dies ist der Fall, wenn die Activity bzw. das Fragment erstmals erzeugt wurde.

In den folgenden beiden Abbildungen sind die zwei Pfade dargestellt, über die in Android der Zustand einer Activity bzw. eines Fragments gespeichert und wieder hergestellt wird:

android save state activity

Zustandsinformationen einer Activity wiederherstellen

android save state fragment

Zustandsinformationen eines Fragments wiederherstellen

Ob die Methode onSaveInstanceState() aufgerufen wird, bevor eine Activity und ihre Fragmente zerstört werden, kann vom Android System nicht garantiert werden. Wenn bspw. der Benutzer die Anwendung mit dem Back-Button schließt, wird der Zustand nicht gesichert. Android nimmt in diesem Fall an, dass der Benutzer die App explizit beendet und beim nächsten Start der App eine frische Anwendung erwartet.

Falls die Methode onSaveInstanceState() aufgerufen wird, dann versucht das Android System den Aufruf vor der onStop() Methode auszuführen und wenn möglich auch vor der onStart() Methode.

Da die onSaveInstanceState() Methode nicht in jedem Fall aufgerufen wird, sollte mit ihr nur der Zustand der Benutzeroberfläche gespeichert werden. Zum Speichern von persistenten Daten sollte sie nicht verwendet werden. Solche Operationen, wie das Speichern von Daten in eine Datenbank, sollten in der onPause() Methode vorgenommen werden.

Sehr detaillierte Informationen über das Verwalten des Lifecycles und das Speichern / Wiederherstellen des Activity-Zustands könnt ihr auf der Android Developer Webseite unter folgenden Links finden:

5.1 Welche Zustandsinformationen speichert das Android System selbständig?

Das Android System ist in der Lage einige der Activity bzw. Fragment Zustandsinformationen selbst zu speichern. Diese Aufgabe übernimmt die Default-Implementierung der onSaveInstanceState() Methode. Daher ist es auch wichtig die Superklassen Implementierung mit super.onSaveInstanceState(savedInstanceState); als Erstes aufzurufen.

Die Default-Implementierung ruft jede onSaveInstanceState() Methode jedes View-Objekts im Layout auf. Dadurch kann jeder View den eigenen Zustand speichern.

Nahezu jedes Widget im Android Framework implementiert die onSaveInstanceState() Methode entsprechend. Ein EditText Widget speichert den eingegebenen Text und ein CheckBox Widget speichert, ob sie aktiviert ist oder nicht.

Es können aber nur die Zustände von Widgets gespeichert werden, wenn diesen eine eigene ID zugewiesen wurde. Besitzt ein Widget keine ID, kann das Android System den Zustand des Widgets nicht speichern.

Die Default-Implementierung von der Methode onSaveInstanceState() hilft den Zustand der Benutzeroberfläche zu speichern, die Membervariablen gehen jedoch verloren. Daher ist es oft notwendig die onSaveInstanceState() Methode zu überschreiben. Dabei darf nicht vergessen werden die Superklassen Implementierung von onSaveInstanceState() zu Beginn aufzurufen. Dieser Fall gilt auch, wenn für das Laden des Zustands die onRestoreInstanceState() überschrieben wird. Auch dann muss die Superklassen Implementierung von onRestoreInstanceState() zu Beginn aufgerufen werden.

5.2 Wie kann die onSaveInstanceState() Methode getestet werden?

Eine sehr einfache Möglichkeit zu überprüfen, ob die eigene App in der Lage ist den Zustand ihrer Activities und Fragmente wieder herzustellen, ist das Rotieren des Android Geräts. Durch den Wechsel der Bildschirmausrichtung (screen orientation), zerstört das Android System die sichtbare Activity und ihre Fragmente.

Auf diese Weise wird vom System sichergestellt, dass die zur Verfügung stehenden Bildschirmressourcen optimal genutzt werden können, bspw. durch alternative Layouts.

Schon alleine aus diesem Grund ist es wichtig, dass die Activities bzw. die Fragmente ihre Benutzeroberfläche vollständig wiederherstellen können. Denn User von mobilen Geräten verändern regelmäßig die Bildschirmausrichtung während des Nutzens.

Wir werden im nächsten Abschnitt den Zustand des AktienlisteFragments speichern und wiederherstellen. Testen werden wir unsere Implementierung, indem wir durch eine Bildschirmrotation das Neuerstellen unserer MainActivity mit dem AktienlisteFragment erzwingen.

5.3 Wann wird die onSaveInstanceState() Methode aufgerufen?

Wie bereits einige Male in diesem Beitrag erwähnt, ruft das Android System die Callback-Methode onSaveInstanceState() nur in bestimmten Situationen auf.

In der folgenden Übersicht sind die Situationen aufgeführt, in denen eine Activity und ihre Fragmente zerstört werden. Dabei ist für jede Situation angemerkt, ob die onSaveInstanceState() Methode aufgerufen wird oder nicht.

Situationen in denen die Activity-Instanz vom Android System zerstört wird:

  • Verlassen der App mit dem Back-Button – Das Android System wertet diese Situation als endgültiges Verlassen der Anwendung. Daher wird die onSaveInstanceState() Methode nicht aufgerufen und somit der Activity-Zustand nicht gespeichert.
  • Mit dem Up-Button zurück navigieren – In der Standardeinstellung der Activity interpretiert das Android System die Situation so, als ob der Benutzer zum Hauptbildschirm der App möchte und nicht das Wiederherstellen des vorherigen Zustands erwartet. Daher wird die onSaveInstanceState() Methode nicht aufgerufen und somit der Activity-Zustand nicht gespeichert.
  • Die Activity wird mit finish() beendet – Die Activity wird manuell beendet. Das Android System versucht daher nicht den Zustand zu erhalten. Auch in diesem Fall wird die onSaveInstanceState() Methode nicht aufgerufen und somit der Activity-Zustand nicht gespeichert.
  • Der App-Prozess wird direkt beendet – Das Android System benötigt Speicherplatz und schließt daher gestoppte Anwendungen, indem es den zugehörigen Prozess direkt beendet. Vorher wird die onSaveInstanceState() Methode aufgerufen und somit der Activity-Zustand gespeichert.
  • Rotation des Bildschirms – In diesem Fall wird die angezeigte Activity vom Android System zerstört und sofort wieder neu erstellt. Vorher wird die onSaveInstanceState() Methode aufgerufen und der Activity-Zustand gespeichert. Diese Situation ist ideal, um die Implementierung der onSaveInstanceState() Methode zu testen.

6. Den Zustand unserer App speichern und wiederherstellen

Unsere Finanz-App kann momentan die aktualisierten Aktiendaten nicht beibehalten, wenn das Android Gerät rotiert oder der Up-Button gedrückt wird. Diese beiden Probleme werden wir nun beheben.

Dazu werden wir in diesem Abschnitt die onSaveInstanceState() Methode der Klasse AktienlisteFragment implementieren und in ihr die aktualisierten Aktiendaten speichern. Die gespeicherten Aktiendaten lesen wir dann in der onCreateView() Methode wieder aus, wenn das Fragment wieder neu erzeugt wird.

Im nächsten Abschnitt werden wir das Problem mit dem Up-Button beheben. Momentan gehen alle Aktiendaten verloren, wenn in der AktiendetailActivity der Up-Button gedrückt wird und dadurch die MainActivity wieder neu erstellt werden muss.

6.1 Implementieren der onSaveInstanceState() Methode des AktienlisteFragments

Durch das Rotieren des Android Smartphones oder Tablet wird die gerade angezeigte Activity vom System zerstört und anschließend wieder neu erstellt. Mit der Activity werden auch alle in ihr enthaltenen Fragmente zerstört und wieder neu erstellt.

Aus diesem Grund gehen auch unsere aktualisierten Aktiendaten bei einer Rotation verloren. Das AktienlisteFragment, welches die Aktiendaten anzeigt, wird zerstört und wieder neu erstellt. Dabei werden jedoch anstelle der aktualisierten Daten die Beispieldaten geladen.

Dieses Fehlverhalten werden wir nun beheben und öffnen dazu in Android Studio die Datei AktienlisteFragment.java.

In der Datei werden wir an drei Stellen im Quellcode Änderungen vornehmen:

  1. Anlegen der Konstante STATE_DATA als Key für das Speichern und Auslesen der Aktiendaten in das Bundle.
  2. Auslesen der gespeicherten Aktiendaten aus dem Bundle in der onCreateView() Methode.
  3. Überschreiben der onSaveInstanceState() Methode und Speichern der aktualisierten Aktiendaten in dem Bundle.

Schritt 1: Direkt zu Beginn der Klasse AktienlisteFragment definieren wir die Konstante STATE_DATA:

AktienlisteFragment.java

// Schlüssel-Konstante für das Speichern und Auslesen der Finanzdaten
static final String STATE_DATA = "Finanzdaten";

Schritt 2: In der onCreateView-Methode fügen wir den gelb markierten Codeblock (Zeile 24 bis 29) ein:

AktienlisteFragment.java

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    Log.v(LOG_TAG, "In Callback-Methode: onCreateView()");

    Log.v(LOG_TAG, "verbose     - Meldung");
    Log.d(LOG_TAG, "debug       - Meldung");
    Log.i(LOG_TAG, "information - Meldung");
    Log.w(LOG_TAG, "warning     - Meldung");
    Log.e(LOG_TAG, "error       - Meldung");

    String [] aktienlisteArray = {
            "Adidas - Kurs: 73,45 €",
            "Allianz - Kurs: 145,12 €",
            "BASF - Kurs: 84,27 €",
            "Bayer - Kurs: 128,60 €",
            "Beiersdorf - Kurs: 80,55 €",
            "BMW St. - Kurs: 104,11 €",
            "Commerzbank - Kurs: 12,47 €",
            "Continental - Kurs: 209,94 €",
            "Daimler - Kurs: 84,33 €"
    };

    if (savedInstanceState != null) {
        // Wiederherstellen der Werte des gespeicherten Fragment-Zustands
        aktienlisteArray = savedInstanceState.getStringArray(STATE_DATA);

        Log.v(LOG_TAG, "Zustand des Fragments wieder hergestellt.");
    }

    List<String> aktienListe = new ArrayList<>(Arrays.asList(aktienlisteArray));

    mAktienlisteAdapter =
            new ArrayAdapter<>(
                    getActivity(), // Die aktuelle Umgebung (diese Activity)
                    R.layout.list_item_aktienliste, // ID der XML-Layout Datei
                    R.id.list_item_aktienliste_textview, // ID des TextViews
                    aktienListe); // Beispieldaten in einer ArrayList


    View rootView = inflater.inflate(R.layout.fragment_aktienliste, container, false);

    ListView aktienlisteListView = (ListView) rootView.findViewById(R.id.listview_aktienliste);
    aktienlisteListView.setAdapter(mAktienlisteAdapter);

    aktienlisteListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
            String aktienInfo = (String) adapterView.getItemAtPosition(position);

            // Intent erzeugen und Starten der AktiendetailActivity mit explizitem Intent
            Intent aktiendetailIntent = new Intent(getActivity(), AktiendetailActivity.class);
            aktiendetailIntent.putExtra(Intent.EXTRA_TEXT, aktienInfo);
            startActivity(aktiendetailIntent);
            }
    });

    mSwipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout_aktienliste);

    mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            aktualisiereDaten();
        }
    });

    return rootView;
}

Mit den Zeilen 24 bis 29 prüfen wir, ob der vorherige Fragment-Zustand gesichert wurde und lesen die gespeicherten Aktiendaten aus dem Bundle aus. Außerdem geben wir noch eine Log-Meldung zur Kontrolle aus.

Schritt 3: Direkt nach der onCreateView-Methode fügen wir die untere Methode onSaveInstanceState ein:

AktienlisteFragment.java

@Override
public void onSaveInstanceState (Bundle savedInstanceState) {
    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);

    Log.v(LOG_TAG, "In Callback-Methode: onSaveInstanceState()");

    int anzahlElemente = mAktienlisteAdapter.getCount();
    String [] aktienlisteArray = new String[anzahlElemente];
    for (int i=0; i < anzahlElemente; i++) {
        aktienlisteArray[i] = mAktienlisteAdapter.getItem(i);
    }

    savedInstanceState.putStringArray(STATE_DATA, aktienlisteArray);
}

In der onSaveInstanceState-Methode rufen wir zuerst die Superklassen Implementierung auf, damit das Android System seinen Teil der Zustandssicherung vornehmen kann. Anschließend geben wir eine Log-Meldung aus, die darüber informiert, dass wir die Methode betreten haben.

Dann folgt das Speichern der aktualisierten Aktiendaten. Die Daten werden in das Bundle savedInstanceState mit Hilfe der putStringArray-Methode abgelegt. Als Key verwenden wir, die in Schritt 1 angelegte Konstante STATE_DATA. Sie bekommt als Wert die aktualisierten Aktiendaten zugewiesen.

Da wir an einigen Stellen Veränderungen in der Datei AktienlisteFragment.java vorgenommen haben, kann es leicht zu Missverständnissen kommen. Daher solltet ihr noch einmal in Ruhe den kompletten Quellcode der Klasse anschauen und mit eurem vergleichen:

AktienlisteFragment.java (als ZIP-Datei gepackt)

Als Nächstes starten wir unsere Android Anwendung wie in Abschnitt 8 beschrieben.

6.2 Testen des Speichern und Wiederherstellen des Fragment-Zustands

In den folgenden drei Abbildungen sind die relevanten Log-Meldungen der AktienlisteFragment-Klasse dargestellt:

android save state

Die Finanz-App wurde gestartet. MainActivity und AktienlisteFragment sind im Resumed-Zustand.

In der oberen Abbildung sind die Log-Meldungen zu sehen, die direkt nach dem Start unserer Finanz-App ausgegeben werden. Das AktienlisteFragment befindet sich zu diesem Zeitpunkt im Resumed-Zustand.

Es ist wichtig, dass in dem Android-Tab in der logcat-Ansicht im Suchfeld der Name der Klasse angegeben ist. In diesem Fall muss AktienlisteFragment in das Suchfeld (Markierung 1 in der oberen Abbildung) eingegeben werden, um nach unseren Log-Meldungen zu filtern.

Jetzt führen wir eine Bildschirmrotation durch, was zum Zerstören und Wiederherstellen des Fragments und den folgenden Log-Meldungen führt:

android save state

Die Bildschirmrotation führt zum Zerstören und wiederherstellen des Fragments.

Bevor die Instanz des Fragments zerstört wird, speichert das Android System den Zustand. Dazu wird die Methode onSaveInstanceState() aufgerufen, in der wir die aktualisierten Aktiendaten in dem Bundle speichern. Der Methodenaufruf erfolgt zwischen den Methoden onPause() und onStop().

Anschließend wird das Fragment zerstört und wieder neu erstellt. In der onCreateView() Methode wird der vorherige Zustand wiederhergestellt und die abgespeicherten Aktiendaten aus dem Bundle ausgelesen.

Als Nächstes beenden wir die Anwendung mit einem Klick auf den Back-Button. Dies führt zu den folgenden Log-Meldungen:

android save state

Die App wird mit dem Back-Button beendet. Die MainActivity und das AktienlisteFragment werden dabei zerstört. Der Zustand wird in diesem Fall nicht gespeichert.

Wie in der oberen Abbildung zu erkennen ist, wird der Zustand des Fragments nicht gespeichert und geht verloren. Dies ist das normale Anwendungsverhalten, da das Android System davon ausgeht, dass der Benutzer kein Interesse mehr am aktuellen Zustand der Activity und ihrer Fragmente besitzt.

Mit den, in diesem Abschnitt, vorgenommenen Quellcode-Änderungen kann unsere Android App nun den Zustand ihres Fragments erhalten. In Abschnitt 8 zeigen wir, was auf dem Bildschirm beim Wiederherstellen des Fragments zu sehen ist.

Jetzt werden wir noch das Problem mit dem Up-Button beheben. Mehr dazu im nächsten Abschnitt.

7. Das Startverhalten (launch mode) der MainActivity festlegen

Ein Task ist eine Ansammlung von Activities mit deren Hilfe der User eine bestimmte Aufgabe ausführen kann. Die Activities sind in einem Stapel, dem sog. Back Stack, angeordnet. Sie sind in der Reihenfolge auf dem Back Stack gelegt worden, in der sie geöffnet worden sind.

Der Home Screen ist der Ausgangspunkt für die meisten Tasks. Startet ein Benutzer eine App, bspw. über ihr Startsymbol, kommt der entsprechende Anwendungstask in den Vordergrund. Existiert noch kein Task für diese Anwendung, wird ein neuer Task erzeugt und die „Main“ Activity der App geöffnet als unterste Activity in dem Back Stack der Anwendung.

Wird in der aktuellen Activity eine neue Activity gestartet, wird die neu gestartete Activity ganz oben auf den Back Stack gelegt. Die vorherige Activity bleibt im Back Stack erhalten, wird jedoch gestoppt.

Drückt der User den Back-Button, wird die geöffnete Activity vom Back Stack entfernt, dann zerstört und die vorherige Activity wieder fortgesetzt. Der Zustand der vorherigen Activity bleibt dabei erhalten, da diese nur gestoppt und nicht zerstört wurde.

Drückt der User solange den Back-Button bis alle Activities vom Back Stack entfernt wurden, dann wird auch der Task beendet.

Mit dem Up-Button verhält es sich anders.

Navigiert ein User mit dem Up-Button von einer Activity zu einer anderen Activity, dann wird das Startverhalten von dem launch mode der Parent Activity festgelegt. Falls für die Parent Activity als launch mode singleTop vorgegeben ist, wird die Parent Activity nach oben auf das Ende des Back Stacks geholt und ihr Zustand wiederhergestellt. Der Navigations-Intent wird dabei von der onNewIntent() Methode der Parent Activity empfangen.

Wenn für die Parent Activity als launch mode jedoch standard festgelegt wurde, werden die aktuelle Activity und die Parent Activity von dem Back Stack entfernt, dann zerstört und anschließend eine neue Instanz der Parent Activity erstellt.

Und genau das ist die Ursache für den Verlust der aktuellen Aktiendaten, wenn aus der AktiendetailActivity über den Up-Button zurück in die MainActivity navigiert wird. Daher werden wir als Nächstes den launch mode unserer MainActivity auf singleTop setzen.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

Eine sehr ausführliche Behandlung des Back Stacks und der launch modes findet ihr wie immer auf der Android Developer Seite und bei StackOverflow:

7.1 Setzen des launch modes der MainActivity auf singleTop

Das Startverhalten (launch mode) einer Activity, also wie und ob sie in dem aktuellen Task gestartet werden soll, kann auf zwei Wege vorgegeben werden:

  • In der Manifest-Datei – Wenn eine Activity in der AndroidManifest.xml Datei deklariert wird, kann ihr Startverhalten mit dem Attribut android:launchMode vorgegeben werden.
  • Nutzen von Intent Flags – Wird eine Activity mit der Methode startActivity() aufgerufen, kann dem Intent ein Flag hinzugefügt werden, welches das Startverhalten der zu startenden Activity festlegt.

In unserem Fall wählen wir den ersten Weg und nehmen eine Änderung in der AndroidManifest.xml Datei unserer Android App vor. Dazu öffnen wir die Datei in Android Studio und fügen der MainActivity das Attribut android:launchMode hinzu:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.programmierenlernenhq.aktiehq.app" >

    <!-- Diese Genehmigung ist notwendig, damit unsere App network sockets öffnen darf. -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:launchMode="singleTop"
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".AktiendetailActivity"
            android:label="@string/title_activity_aktiendetail"
            android:parentActivityName=".MainActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="de.programmierenlernenhq.aktiehq.app.MainActivity" />
        </activity>
        <activity
            android:name=".EinstellungenActivity"
            android:label="@string/title_activity_einstellungen"
            android:parentActivityName=".MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="de.programmierenlernenhq.aktiehq.app.MainActivity" />
        </activity>
    </application>

</manifest>

In dem oberen Quellcode wurde nur eine Änderung vorgenommen und zwar haben wir in Zeile 14 das Attribut android:launchMode der MainActivity auf singleTop gesetzt.

Das Attribut android:launchMode gibt vor, wie eine Activity in einem Task gestartet werden soll, zum Beispiel ob eine weitere Instanz der Activity erzeugt oder eine bereits vorhandene Activity fortgesetzt werden soll.

In Android sind vier verschiedene launch modes definiert: standard, singleTop, singleTask und singleInstance. Für unsere Finanz-App ist singleTop als Startverhalten die richtige Wahl.

Als Nächstes starten wir unsere Android Anwendung wie in Abschnitt 8 beschrieben.

7.2 Testen des Startverhaltens (launch mode) der MainActivity

In den folgenden drei Abbildungen sind die relevanten Log-Meldungen der MainActivity-Klasse dargestellt:

Es ist wichtig, dass in dem Android-Tab in der logcat-Ansicht im Suchfeld der Name der Klasse angegeben ist. In diesem Fall muss MainActivity in das Suchfeld (Markierung 1 in der unteren Abbildung) eingegeben werden, um nach unseren Log-Meldungen zu filtern.

android launch mode

Unsere Finanz-App wurde gestartet. Die MainActivity befindet sich im Resumed-Zustand.

Die obere Abbildung zeigt die, von der MainActivity, ausgegebenen Log-Meldungen kurz nachdem unsere App gestartet wurde. Wie zu erkennen ist, befindet sich die Activity im Resumed-Zustand.

Durch einen Klick auf ein Listenelement starten wir die AktiendetailActivity, dadurch wechselt die MainActivity in den Stopped-Zustand und die folgenden Log-Meldungen werden ausgegeben:

android launch mode

AktiendetailActivity wurde gestartet und überdeckt die MainActivity. Die MainActivity ist jetzt im Stopped-Zustand.

Als Nächstes navigieren wir mit dem Up-Button von der AktiendetailActivity zurück in die MainActivity. Da wir den launch mode der MainActivity auf singleTop gesetzt haben, wird die bereits vorhandene Instanz der MainActivity fortgesetzt.

In der unteren Abbildung sind die Log-Meldungen zu diesem Vorgang angegeben:

android launch mode

Mit Up-Button zurück in die MainActivity navigiert. Die vorhandene Instanz der MainActivity wird fortgesetzt.

Es wird keine neue Instanz der MainActivity erstellt, da bereits eine Instanz ganz oben auf dem Back Stack liegt und singleTop dafür sorgt, dass diese Instanz fortgesetzt wird.

Somit gehen auch keine Informationen verloren und die aktualisierten Aktiendaten bleiben erhalten.

7.3 Startverhalten der MainActivity ohne singleTop

Zur Vollständigkeit haben wir das Startverhalten der MainActivity für den Fall dokumentiert, in dem als launch mode nicht singleTop sondern standard gesetzt ist.

In der unteren Abbildung ist der Log-Output für diesen Fall dargestellt:

android launch mode

Das Startverhalten der MainActivity wenn als launch mode nicht singleTop gesetzt ist

Es wurde zuerst die App gestartet, dann die AktiendetailActivity aufgerufen und von dieser mittels Up-Button zurück in die MainActivity navigiert. Also der gleiche Ablauf wie im vorherigen Abschnitt.

Da aber als launch mode der MainActivity diesmal standard vorgegeben ist, wird die vorhandene Instanz der MainActivity zerstört und anschließend eine neue Instanz von ihr erstellt. Alle Informationen gehen in diesem Fall verloren, auch die vorher geladenen Aktiendaten.

8. Installieren und Testen unserer Android App

Alle notwendigen Änderungen an den Quelltexten unseres Android Projekts sind vorgenommen. Nun wollen wir unsere App testen. Dazu installieren wir die Anwendung auf unser angeschlossenes Android Gerät (Smartphone oder Tablet) und lassen sie dort ausführen.

Um die App auf dem Android Gerät starten zu können, müssen alle Schritte von Teil 3 des Android Tutorials befolgt worden sein.

Hier noch einmal in Kürze die wichtigsten Arbeitsschritte.

Zuerst schließen wir unser Android Gerät an den PC an und stellen eine Verbindung über die ADB (Android Debug Bridge) her. Danach klicken wir auf das Run 'app'-Symbol. Unser Android Projekt wird dadurch neu erstellt und auf dem angeschlossenen Gerät ausgeführt.

Das Run 'app'-Symbol befindet sich in der oberen Menüleiste, siehe folgende Abbildung:

android studio project avd run

Android App über das Run App-Symbol starten

Nach einigen Momenten öffnet sich der Choose Device-Dialog. In ihm nehmen wir die folgenden Einstellungen vor:

  1. Radio Button Choose a running device aktivieren
  2. Das angeschlossene Android Gerät auswählen
  3. Mit einem Klick auf den OK-Button die Installation unserer App auf das Gerät starten
android choose device

Auswählen des angeschlossenen Android Geräts zum Aufspielen unserer App

Der Dialog schließt sich und unsere Android App wird auf das angeschlossene Gerät übertragen und installiert. Die Installation dauert nur einen kurzen Augenblick und verläuft fast unbemerkt im Hintergrund. Danach wird unsere App automatisch gestartet.

8.1 Wiederherstellen des Fragment-Zustands bei unserer Android App testen

Unsere Android App ist gestartet. Wir aktualisieren die Aktiendaten mit einer Swipe Down-Geste. Als Nächstes drehen wir das Android Gerät um 90° und erzwingen dadurch das Neuerstellen des View-Layouts.

In der folgenden Abbildung ist das beschriebene Vorgehen dargestellt:

android lifecycle rotation

Die Finanz-App kann jetzt auch bei einer Bildschirmrotation die Aktiendaten beibehalten

Wie zu erkennen ist, kann unsere App jetzt die aktualisierten Aktiendaten beibehalten. Die Daten werden vor dem Zerstören der Activity und ihrem Fragment abgespeichert und anschließend beim Wiederherstellen der beiden Container aus dem Bundle geladen.

In der nächsten Abbildung ist das Verhalten unserer Android Anwendung während der Up-Navigation dargestellt:

android lifecycle up button

Die Finanz-App kann jetzt auch bei der Up-Navigation die Aktiendaten beibehalten

Auch diesmal haben wir die Aktienliste mit aktuellen Daten gefüllt. Mit einem Klick auf ein Element der Liste gelangen wir in die AktiendetailActivity, wodurch die MainActivity gestoppt wird.

Mit einem Klick auf den Up-Button gelangen wir von der AktiendetailActivity wieder zurück in die MainActivity. Diese wird nicht neu erstellt, sondern ihre Instanz wieder fortgesetzt. Dadurch gehen keine Daten verloren und die aktualisierten Aktiendaten bleiben erhalten.

8.2 Video – Fragment-Zustand in Android wiederherstellen

Nun möchten wir unsere Android App auch einmal in der aktuellen Entwicklungsstufe im Video sehen. Dazu haben wir das folgende kurze Video erstellt, in dem die neue Funktion unserer App vorgestellt wird:

In dem oberen Video ist die aktuelle Version unserer Finanz-App zu sehen. Zu Beginn laden wir die aktuellen Aktiendaten mit Hilfe der Swipe Down-Geste. Anschließend führen wir eine Bildschirmdrehung durch. Dabei wird die Activity vom Android System zerstört und wiederhergestellt, ebenso ihr zugewiesenes Fragment.

Da unsere App die onSaveInstanceState() Methode implementiert, können wir die geladenen Aktiendaten vor dem Zerstören der Instanz speichern und bei dem Wiederherstellen der Activity und des Fragments aus dem Bundle auslesen. Dadurch gehen die aktualisierten Aktiendaten nicht mehr verloren, sondern bleiben auch bei einer Rotation des Android Geräts erhalten.

Danach navigieren wir mit einem Klick auf ein Listenelement in die AktiendetailActivity. Anschließend drücken wir auf den Up-Button und gelangen in die MainActivity zurück. Auch diesmal bleiben die aktualisierten Aktiendaten erhalten, da wir den launch mode der MainActivity auf singleTop gesetzt haben.

Zusammenfassung

In dieser sehr großen Lektion haben wir den Lebenszyklus von Activities und Fragmenten in Android intensiv behandelt. Das Verständnis dieser Lifecycles ist besonders für die Entwicklung von komplexen Anwendungen in Android erforderlich.

Mit dem erlangten Wissen waren wir in der Lage den Zustand der MainActivity und ihres Fragments, dem AktienlisteFragment, zu erhalten. Denn um den Activity- und Fragment-Zustand speichern und wiederherstellen zu können, müssen die Lifecycle Callbacks gut verstanden sein.

Außerdem konnten wir ein zweites Problem lösen, indem wir das Startverhalten der MainActivity geändert haben. Durch das Setzen des launch modes auf singleTop, wird die vorhandene Instanz der MainActivity bei der Up-Navigation weiterverwendet und keine neue Instanz erstellt. Somit gehen auch keine Daten mehr verloren, wenn von der AktiendetailActivity aus, mit dem Up-Button, zurück in die MainActivity navigiert wird.



Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.