Stand: 09.07.2018

10.1 Warum Lifecycle?

Bei einer Android-App denken wir in Activities. Mit dem Lifecycle (engl. für Lebenszyklus) einer Activity bezeichnet man verschiedene Phasen dieser Activity, wie z.B. das erste Starten, das Sichtbarwerden, das Wechseln in eine andere Activity, das Wechseln in eine andere App und das Schließen der App.

Man spricht auch davon, dass eine App im Vordergrund ist oder im Hintergrund (aber nicht beendet).

Aus Sicht des Programmierers ist dies wichtig für Fragen wie: Sind meine Daten noch da? Aus Sicht von Android ist dies wichtig, um die Performance des Geräts optimal zu halten, denn für eine App gibt es potentiell viele Activities und für das Gerät gibt es potentiell viele Apps, die alle gleichzeitig "laufen" und Ressourcen verbrauchen können (Rechenzeit, Lese-/Schreibzugriffe auf Speicher und Netz). Diesen Verbrauch möchte man so gering wie möglich halten.

Wenn eine Activity einer App bereits läuft, gibt es mindestens vier Verläufe, bei denen man sich fragen kann, was mit der Activity und Ihren Daten - genauer gesagt: ihren Instanzvariablen - passiert:

Mögliche Ereignisse im Leben einer App

10.2 Callback-Methoden

Android stellt den Programmierern mehrere Methoden bereit, die vom System aufgerufen werden, wenn bestimmte Ereignisse (engl. events) im Leben einer Activity eintreten. Methoden, die bei bestimmten Events aufgerufen werden, nennt man auch Callback-Methoden oder Callbacks. Als Programmiererin kann man sich "einklinken", indem man die entsprechenden Methoden *überschreibt.

Sie kennen bereits die Methode onCreate(), die Sie überschreiben müssen und wo Sie Code zu Initialisierung Ihrer Activity hineinschreiben. Es gibt entsprechend noch weitere Methoden, die von der Activity-Klasse bereit gestellt werden, und die man optional überschreiben kann, um z.B. Daten zu sichern, zu laden oder um ressourcenfressende Animationen zu pausieren.

10.2.1 Zustände

Jede Activity einer App hat einen Lifecycle, d.h. jede Activity befindet sich zur Laufzeit, sofern sie mindestens einmal vom Benutzer geöffnet wurde, in einem von sechs Zuständen:

Im letzten Zustand verschwindet die Activity.

Den Übergang von einem Zustand in den nächsten (oder ganz zu Beginn in den ersten Zustand) nennt man auch ein Lifecycle-Event und dafür gibt es mehrere Callback-Methoden, die man als Programmierer*in überschreiben kann:

Welchen Zustand die Callback-Methoden jeweils einleiten, sehen Sie in folgendem Graphen:

Graph mit Callbacks und Zuständen im Lifecycle einer Activity

(Quelle: Google / Android Developers Training, Lizenz CC-BY-NC)

10.2.2 Callbacks

Wir sehen uns die verschiedenen Abläufe von Lifecycle-Events und entsprechenden Aufrufen der Callback-Methoden für typische Fälle an.

Activity starten

Beim ersten Starten einer Activity werden die folgenden Methoden in dieser Reihenfolge aufgerufen.

Lebenszyklus: Activity starten

Sie überschreiben normalerweise onCreate, um UI-Komponenten hinzuzufügen, zu ändern oder mit Listenern auszustatten. Die App ist beim Abarbeiten dieser Methode nocht nicht sichtbar.

Die Methode onStart wird anschließend aufgerufen und i.d.R. nicht überschrieben.

Zum Schluss kommt die Methode onResume, die auch in einigen anderen Kontexten aufgerufen wird (siehe Abschnitt "Activity wiederaufnehmen"). Hier kommt Code rein, der z.B. die Instanzvariablen auf den aktuellen Zustand bringen soll. Beim Abarbeiten dieser Methode ist die App bereits sichtbar. Diese Methode ist z.B. dann interessant, wenn Sie beim jedem "Wiedereintritt" in die App Dinge tun möchten.

Etwas unerwartet ist vielleicht, dass auch beim Ändern der Konfiguration die Activity komplett neu gestartet wird. Dazu gehört:

Der Grund ist, dass hier komplett neue Layouts (in Form von speziellen XML-Layout-Dateien) aufgerufen werden können (siehe Modul Layout II).

Diese Reihe wird immer in folgenden Situationen aufgerufen:

Activity pausieren

Wenn Sie in eine andere Activity wechseln, wird die aktuelle Activity gestoppt, aber nicht zerstört. Die Activity durchläuft die folgenden Methoden:

Lebenszyklus: Activity wechseln

Die Methode onPause leitet einen Zustand ein, wo die Activity nur teilweise sichtbar ist. Zum Beispiel, wenn ein teiltransparentes Dialogfenster die Activity teilweise verdeckt. Es ist ein Übergangszustand. Es kann z.B. sinnvoll sein, hier bereits ein laufendes Video zu pausieren. Bei Animationen kann es genauso sinnvoll sein, sie weiter laufen zu lassen und erst später zu stoppen. Hier ist es nur wichtig, dass Sie keine langwierigen Prozesse (z.B. Speichern) ausführen, um den Fluss der Übergänge nicht zu belasten.

Sobald onStop aufgerufen ist, ist die Activity nicht mehr sichtbar. Spätestens hier kann man also Videos/Animationen stoppen/pausieren. Auch Prozesse wie das Sichern von Daten, die evtl. mehr Zeit benötigen, können hier statt in onPause ausgeführt werden.

Diese Reihe wird immer in folgenden Situationen aufgerufen:

Activity wiederaufnehmen

Wenn die Activity schon mal aufgerufen wurde und man aus einer anderen Activity zurückkehrt, werden folgende Methoden durchlaufen:

Lebenszyklus: Activity wieder aufnehmen

Diese Reihe wird immer in folgenden Situationen aufgerufen (unter der Voraussetzung, dass die Activity schon 1x vorher aufgerufen worden war):

Activity beenden

Wenn Sie die App schließen, passiert in allen bisher aufgerufenen Activities der App folgendes:

Lebenszyklus: App schließen

Wenn Sie also eine App mit drei Activities haben, die Sie alle mindestens einmal aufgerufen haben, dann werden für alle drei Activities diese Methoden aufgerufen.

Zu beachten ist, dass eine Sub-Activity, sobald sie mit finish beendet wird, auch zerstört wird, d.h. diese Reihe wird dann schon durchlaufen.

Da es vorkommen kann, dass onDestroy nicht aufgerufen wird, sollten wichtige Aktionen wie das Speichern von Daten, lieber in onStop durchgeführt werden.

Diese Reihe wird immer in folgenden Situationen aufgerufen:

Überschreiben

Wie erwähnt können Sie als Programmiererin jede der erwähnten Methoden *überschreiben, um Code an Zustandsübergängen auszuführen. Das sieht das ungefähr so aus:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // Ihr Code
}

protected void onStart() {
    super.onStart();
    // Ihr Code
}

protected void onResume() {
    super.onResume();
    // Ihr Code
}

protected void onRestart() {
    super.onRestart();
    // Ihr Code
}

protected void onPause() {
    super.onPause();
    // Ihr Code
}

protected void onStop() {
    super.onStop();
    // Ihr Code
}

protected void onDestroy() {
    super.onDestroy();
    // Ihr Code
}

Außer bei onCreate bleibt es Ihnen überlassen, ob Sie die jeweilige Methode überschreiben oder nicht. Der Aufruf von z.B. super.onStart bedeutet, dass die entsprechende Methode der Oberklasse aufgerufen wird und sollte man zur Sicherheit im Code lassen.

Überblick

Hier sehen wir die möglichen Abfolgen im Überblick:

Lebenszyklus-Überblick

Wir haben hier nicht alle Fälle besprochen. Ein sehr guter und vollständiger Überblick zum Thema findet man in folgendem Video: The Activity Lifecycle Explained

10.3 Activity-Zustandsobjekt (Bundle)

Es wurde schon erwähnt, dass das Ändern der "Konfiguration" die Activity schließt und erneut onCreate aufruft. Zur Erinnerung, das Ändern der Konfiguration heißt

Dabei gehen z.B. die Werte aller Instanzvariablen verloren. Wir haben bereits in den Modulen Daten I und Daten II Lösungen gesehen, wie man Daten zwischenzuspeichern kann. Für kleinteilige Informationen wie Zahlen und Texte, die man sich merken möchte, um sie nach der Konfigurationsänderung wieder aufzunehmen, gibt es zusätzlich in jeder Activity ein Zustandsobjekt vom Typ Bundle und eine zugehörige Methode onSaveInstanceState.

Die Callback-Methode onSaveInstanceState wird immer dann aufgerufen, wenn z.B. eine Orientierungsänderung eintritt. Dort bekommt man ein Objekt vom Type Bundle, das man benutzen kann, um Informationen zu speichern. Es handelt sich - wie schon bei den Preferences - um eine einfache Tabelle.

Wenn Sie z.B. eine Instanzvariable counter vom Typ int haben (für eine einfache Zähler-App), dann können Sie diese in der Tabelle im Bundle ablegen:

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("zählstand", counter);
}

Wann genau rufen Sie diese Info wieder auf? Hier gibt es zwei Möglichkeiten. Entweder Sie nutzen eine weitere Callback-Methode namens onRestoreInstanceState oder - noch besser (und empfohlen) - Sie erledigen das direkt in onCreate. Auch dort haben Sie ein Objekt vom Typ Bundle und genau dort steht die Info drin. Beim ersten Aufruf von onCreate ist dies Objekt allerdings null, d.h. Sie testen das Objekt erst auf null und lesen gegebenenfalls die Infos aus.

In unserem Beispiel:

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

    if (savedInstanceState != null) {
        counter = savedInstanceState.getInt("zählstand");
    }

    ...
}

10.4 Zusammenfassung

Jede Activity einer App hat einen Lifecycle, d.h. jede Activity befindet sich zur Laufzeit, sofern sie mindestens einmal vom Benutzer geöffnet wurde, in einem von sechs Zuständen:

  1. Created
  2. Started
  3. Resumed
  4. Paused
  5. Stopped
  6. Destroyed

Im Zustand "Destroyed" verschwindet die Activity.

Als Programmierer kann man sich in jeden Übergang zu einem der Zustände "einklinken", indem man eine Callback-Methode in der entsprechenden Activity-Klasse überschreibt (wobei onCreate immer überschrieben wird):

Weiterhin kann man den Zustand einer Activity bei Konfigurationsänderungen (z.B. Änderung der Orientierung oder Spracheinstellung) speichern, indem man die Methode onSaveInstanceState überschreibt und die dort in einem Bundle-Objekt gespeicherten Informationen z.B. in onCreate verwendet.

10.5 Übungen

(A) ZählerApp v3

Implementieren Sie eine dritte Version der ZählerApp (siehe auch Module Daten I und Daten II), diesmal mit Hilfe von des Zustandsobjekts vom Typ Bundle und der Callback-Methode onSaveInstanceState.

Zähler-App

(B) Lifecycle testen

Um den Lifecycle zu testen, empflieht es sich, eine App zu schreiben, in der man alle Methoden in MainActivity überschreibt und mit Log-Output versieht (siehe Modul Hilfe zum Thema Logging).

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.i("App", "++++++++++++++++++++++++++++++++++++ MAIN onCreate");
}

protected void onStart() {
    super.onStart();
    Log.i("App", "++++++++++++++++++++++++++++++++++++ MAIN onStart");
}

protected void onResume() {
    super.onResume();
    Log.i("App", "++++++++++++++++++++++++++++++++++++ MAIN onResume");
}

protected void onRestart() {
    super.onRestart();
    Log.i("App", "++++++++++++++++++++++++++++++++++++ MAIN onRestart");
}

protected void onPause() {
    super.onPause();
    Log.i("App", "++++++++++++++++++++++++++++++++++++ MAIN onPause");
}

protected void onStop() {
    super.onStop();
    Log.i("App", "++++++++++++++++++++++++++++++++++++ MAIN onStop");
}

protected void onDestroy() {
    super.onDestroy();
    Log.i("App", "++++++++++++++++++++++++++++++++++++ MAIN onDestroy");
}

Sinnvoll ist weiterhin, eine zweite Activity zu schreiben mit analogen Log-Nachrichten und jede Activity mit einem Button zum Übergang zu versehen.

Lebenszyklus-Übung

Sie können mit einer solchen App auch prüfen, was passiert, wenn Sie auf die Buttons Home, Letzte Applikationen oder Back drucken.

In den Screenshots oben sehen Sie einen Button "Make Line in Log", mit dem Sie eine Trennzeile ins Log-Fenster schreiben:

findViewById(R.id.buttonLog).setOnClickListener(v -> {
  Log.i("App", "#######################################################");
});