Dieses Codelab ist Teil des Kurses „Grundlagen von Android und Kotlin“. Sie können diesen Kurs am besten nutzen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Alle Codelabs des Kurses sind auf der Landingpage für Codelabs zu den Grundlagen von Android und Kotlin aufgeführt.
Einführung
Im letzten Codelab haben Sie die Lebenszyklen von Activity und Fragment kennengelernt und die Methoden untersucht, die aufgerufen werden, wenn sich der Lebenszyklusstatus in Aktivitäten und Fragmenten ändert. In diesem Codelab sehen Sie sich den Aktivitätslebenszyklus genauer an. Außerdem erfahren Sie mehr über die Android Jetpack-Lifecycle-Bibliothek, mit der Sie Lifecycle-Ereignisse mit besser organisiertem und leichter zu wartendem Code verwalten können.
Was Sie bereits wissen sollten
- Was eine Aktivität ist und wie Sie eine in Ihrer App erstellen.
- Die Grundlagen der Lebenszyklen von
ActivityundFragmentsowie die Callbacks, die aufgerufen werden, wenn eine Aktivität zwischen den Status wechselt. - Wie Sie die
onCreate()- undonStop()-Lebenszyklus-Callback-Methoden überschreiben, um Vorgänge zu verschiedenen Zeiten im Lebenszyklus der Aktivität oder des Fragments auszuführen.
Lerninhalte
- Wie Sie Teile Ihrer App in den Lifecycle-Callbacks einrichten, starten und beenden.
- Wie Sie die Android Lifecycle-Bibliothek verwenden, um einen Lifecycle-Observer zu erstellen und den Lebenszyklus von Aktivitäten und Fragmenten einfacher zu verwalten.
- Wie sich das Beenden von Android-Prozessen auf die Daten in Ihrer App auswirkt und wie Sie diese Daten automatisch speichern und wiederherstellen können, wenn Android Ihre App schließt.
- Wie sich die Geräteausrichtung und andere Konfigurationsänderungen auf die Lebenszyklusstatus auswirken und den Status Ihrer App beeinflussen.
Aufgabe
- Ändern Sie die DessertClicker-App so, dass sie eine Timerfunktion enthält, und starten und stoppen Sie den Timer zu verschiedenen Zeiten im Aktivitätslebenszyklus.
- Ändern Sie die App so, dass die Android-Lifecycle-Bibliothek verwendet wird, und wandeln Sie die
DessertTimer-Klasse in einen Lifecycle-Observer um. - Richten Sie die Android Debug Bridge (
adb) ein und verwenden Sie sie, um das Herunterfahren des App-Prozesses und die dann auftretenden Lebenszyklus-Callbacks zu simulieren. - Implementieren Sie die Methode
onSaveInstanceState(), um App-Daten beizubehalten, die verloren gehen können, wenn die App unerwartet geschlossen wird. Fügen Sie Code hinzu, um diese Daten wiederherzustellen, wenn die App neu gestartet wird.
In diesem Codelab erweitern Sie die DessertClicker-App aus dem vorherigen Codelab. Sie fügen einen Hintergrund-Timer hinzu und konvertieren die App dann für die Verwendung der Android Lifecycle Library.

Im vorherigen Codelab haben Sie gelernt, wie Sie den Aktivitäts- und Fragmentlebenszyklus beobachten können, indem Sie verschiedene Lebenszyklus-Callbacks überschreiben und protokollieren, wann das System diese Callbacks aufruft. In dieser Aufgabe sehen Sie sich ein komplexeres Beispiel für die Verwaltung von Lebenszyklusaufgaben in der DessertClicker-App an. Sie verwenden einen Timer, der jede Sekunde eine Log-Anweisung mit der Anzahl der Sekunden ausgibt, die er bereits läuft.
Schritt 1: DessertTimer einrichten
- Öffnen Sie die DessertClicker App aus dem letzten Codelab. Wenn Sie die App nicht haben, können Sie DessertClickerLogs hier herunterladen.
- Maximieren Sie in der Ansicht Project (Projekt) java > com.example.android.dessertclicker und öffnen Sie
DessertTimer.kt. Derzeit ist der gesamte Code auskommentiert, sodass er nicht als Teil der App ausgeführt wird. - Wählen Sie den gesamten Code im Editorfenster aus. Wählen Sie Code > Comment with Line Comment (Code > Mit Zeilenkommentar kommentieren) aus oder drücken Sie
Control+/(Command+/auf einem Mac). Mit diesem Befehl werden alle Codezeilen in der Datei auskommentiert. (In Android Studio werden möglicherweise Fehler zu nicht aufgelösten Referenzen angezeigt, bis Sie die App neu erstellen.) - Die Klasse
DessertTimerenthältstartTimer()undstopTimer(), mit denen der Timer gestartet und gestoppt wird. WennstartTimer()ausgeführt wird, gibt der Timer jede Sekunde eine Log-Nachricht mit der Gesamtzahl der Sekunden aus, die seit dem Start vergangen sind. Die MethodestopTimer()stoppt wiederum den Timer und die Log-Anweisungen.
- Öffnen Sie
MainActivity.kt. Fügen Sie oben in der Klasse, direkt unter der VariablendessertsSold, eine Variable für den Timer hinzu:
private lateinit var dessertTimer : DessertTimer;- Scrollen Sie nach unten zu
onCreate()und erstellen Sie direkt nach dem Aufruf vonsetOnClickListener()ein neuesDessertTimer-Objekt:
dessertTimer = DessertTimer()
Nachdem Sie nun ein Dessert-Timer-Objekt haben, sollten Sie überlegen, wann der Timer gestartet und gestoppt werden soll, damit er nur läuft, wenn die Aktivität auf dem Bildschirm angezeigt wird. In den nächsten Schritten sehen Sie sich einige Optionen an.
Schritt 2: Timer starten und stoppen
Die Methode onStart() wird kurz vor dem Sichtbarwerden der Aktivität aufgerufen. Die onStop()-Methode wird aufgerufen, nachdem die Aktivität nicht mehr sichtbar ist. Diese Rückrufe eignen sich gut, um den Timer zu starten und zu stoppen.
- Starte in der Klasse
MainActivityden Timer imonStart()-Callback:
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}- Stoppe den Timer in
onStop():
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}- Kompilieren Sie die App und führen Sie sie aus. Klicken Sie in Android Studio auf den Bereich Logcat. Geben Sie im Logcat-Suchfeld
dessertclickerein, um nach den KlassenMainActivityundDessertTimerzu filtern. Sobald die App gestartet wird, beginnt auch der Timer sofort zu laufen.
- Klicken Sie auf die Schaltfläche Zurück. Der Timer wird wieder angehalten. Der Timer wird beendet, weil sowohl die Aktivität als auch der Timer, den sie steuert, beendet wurden.
- Kehren Sie über den Bildschirm mit den zuletzt verwendeten Apps zur App zurück. In Logcat sehen Sie, dass der Timer bei 0 neu startet.
- Klicken Sie auf die Schaltfläche Freigeben. Beachten Sie, dass der Timer in Logcat weiterhin läuft.

- Klicken Sie auf die Schaltfläche Startseite. Beachten Sie, dass der Timer in Logcat nicht mehr läuft.
- Kehren Sie über den Bildschirm „Zuletzt verwendet“ zur App zurück. In Logcat sehen Sie, dass der Timer wieder an der Stelle fortgesetzt wird, an der er unterbrochen wurde.
- Kommentieren Sie in
MainActivityin der MethodeonStop()den Aufruf vonstopTimer()aus. Durch Auskommentieren vonstopTimer()wird der Fall veranschaulicht, in dem Sie einen Vorgang inonStart()starten, aber vergessen, ihn inonStop()wieder zu beenden. - Kompilieren und führen Sie die App aus und klicken Sie nach dem Start des Timers auf die Startbildschirmtaste. Auch wenn die App im Hintergrund ausgeführt wird, läuft der Timer und es werden kontinuierlich Systemressourcen verwendet. Wenn der Timer weiterläuft, führt das zu einem Speicherleck in Ihrer App und ist wahrscheinlich nicht das gewünschte Verhalten.
Im Allgemeinen gilt: Wenn Sie etwas in einem Callback einrichten oder starten, beenden oder entfernen Sie es im entsprechenden Callback. So vermeiden Sie, dass etwas ausgeführt wird, wenn es nicht mehr benötigt wird.
- Entfernen Sie das Kommentarzeichen in der Zeile in
onStop(), in der Sie den Timer stoppen. - Schneiden Sie den
startTimer()-Aufruf ausonStart()aus und fügen Sie ihn inonCreate()ein. Diese Änderung zeigt den Fall, in dem Sie eine Ressource sowohl inonCreate()initialisieren als auch starten, anstatt sie mitonCreate()zu initialisieren und mitonStart()zu starten. - Kompilieren Sie die App und führen Sie sie aus. Der Timer sollte wie erwartet starten.
- Klicken Sie auf „Zuhause“, um die App zu beenden. Der Timer wird wie erwartet angehalten.
- Kehren Sie über den Bildschirm mit den zuletzt verwendeten Apps zur App zurück. Beachten Sie, dass der Timer in diesem Fall nicht neu gestartet wird, da
onCreate()nur beim Start der App aufgerufen wird, nicht wenn eine App in den Vordergrund zurückkehrt.
Wichtige Hinweise:
- Wenn Sie eine Ressource in einem Lebenszyklus-Callback einrichten, müssen Sie sie auch wieder abbauen.
- Richten Sie die Testumgebung in entsprechenden Methoden ein und bauen Sie sie wieder ab.
- Wenn Sie etwas in
onStart()einrichten, müssen Sie es inonStop()wieder beenden oder entfernen.
In der DessertClicker App ist es relativ einfach zu erkennen, dass der Timer, der in onStart() gestartet wurde, in onStop() gestoppt werden muss. Es gibt nur einen Timer, daher ist es nicht schwer, sich zu merken, wie man ihn stoppt.
In einer komplexeren Android-App richten Sie möglicherweise viele Dinge in onStart() oder onCreate() ein und bauen sie dann alle in onStop() oder onDestroy() wieder ab. Beispielsweise müssen Sie möglicherweise Animationen, Musik, Sensoren oder Timer einrichten und wieder entfernen sowie starten und stoppen. Wenn Sie eine vergessen, führt das zu Fehlern und Problemen.
Die Lifecycle-Bibliothek, die Teil von Android Jetpack ist, vereinfacht diese Aufgabe. Die Bibliothek ist besonders nützlich, wenn Sie viele sich bewegende Teile verfolgen müssen, von denen sich einige in unterschiedlichen Lebenszyklusphasen befinden. Die Bibliothek kehrt die Funktionsweise von Lebenszyklen um: Normalerweise teilt die Aktivität oder das Fragment einer Komponente (z. B. DessertTimer) mit, was bei einem Lebenszyklus-Callback zu tun ist. Wenn Sie jedoch die Lifecycle-Bibliothek verwenden, überwacht die Komponente selbst die Änderungen des Lebenszyklus und führt dann die erforderlichen Aktionen aus.
Die Lifecycle-Bibliothek besteht aus drei Hauptteilen:
- Lebenszyklus-Inhaber, also die Komponenten, die einen Lebenszyklus haben (und ihn daher „besitzen“).
ActivityundFragmentsind die Eigentümer des Lebenszyklus. Lebenszyklusinhaber implementieren dieLifecycleOwner-Schnittstelle. - Die Klasse
Lifecycle, die den tatsächlichen Status eines Lifecycle-Inhabers enthält und Ereignisse auslöst, wenn sich der Lifecycle ändert. - Lifecycle-Beobachter, die den Lebenszyklusstatus beobachten und Aufgaben ausführen, wenn sich der Lebenszyklus ändert. Lifecycle-Beobachter implementieren die
LifecycleObserver-Schnittstelle.
In dieser Aufgabe konvertieren Sie die DessertClicker-App, sodass sie die Android-Lebenszyklusbibliothek verwendet. Außerdem erfahren Sie, wie die Bibliothek die Arbeit mit den Lebenszyklen von Android-Aktivitäten und ‑Fragmenten erleichtert.
Schritt 1: DessertTimer in einen LifecycleObserver umwandeln
Ein wichtiger Teil der Lifecycle-Bibliothek ist das Konzept der Lifecycle-Beobachtung. Durch die Beobachtung können Klassen (z. B. DessertTimer) den Aktivitäts- oder Fragmentlebenszyklus kennen und sich als Reaktion auf Änderungen an diesen Lebenszyklusstatus selbst starten und beenden. Mit einem Lifecycle-Observer können Sie die Verantwortung für das Starten und Stoppen von Objekten aus den Methoden für Aktivitäten und Fragmente entfernen.
- Öffnen Sie den Kurs
DesertTimer.kt. - Ändern Sie die Klassensignatur der Klasse
DessertTimerso:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {Diese neue Klassendefinition hat zwei Auswirkungen:
- Der Konstruktor verwendet ein
Lifecycle-Objekt, das den Lebenszyklus darstellt, den der Timer beobachtet. - Die Klassendefinition implementiert die
LifecycleObserver-Schnittstelle.
- Fügen Sie unter der Variablen
runnableeineninit-Block zur Klassendefinition hinzu. Verwenden Sie iminit-Block die MethodeaddObserver(), um das vom Inhaber (der Aktivität) übergebene Lifecycle-Objekt mit dieser Klasse (dem Observer) zu verbinden.
init {
lifecycle.addObserver(this)
}- Annotieren Sie
startTimer()mit@OnLifecycleEvent annotationund verwenden Sie das LebenszyklusereignisON_START. Alle Lifecycle-Ereignisse, die Ihr Lifecycle-Observer beobachten kann, befinden sich in der KlasseLifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {- Wiederholen Sie den Vorgang für
stopTimer()und verwenden Sie das EreignisON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()Schritt 2: MainActivity ändern
Ihre MainActivity-Klasse ist bereits ein Lifecycle-Inhaber durch Vererbung, da die FragmentActivity-Superklasse LifecycleOwner implementiert. Daher müssen Sie nichts unternehmen, um den Lebenszyklus Ihrer Aktivität zu berücksichtigen. Sie müssen lediglich das Lebenszyklusobjekt der Aktivität an den DessertTimer-Konstruktor übergeben.
- Öffnen Sie
MainActivity. Ändern Sie in der MethodeonCreate()die Initialisierung vonDessertTimer, umthis.lifecycleeinzuschließen:
dessertTimer = DessertTimer(this.lifecycle)Die lifecycle-Property der Aktivität enthält das Lifecycle-Objekt, das zu dieser Aktivität gehört.
- Entfernen Sie den Aufruf von
startTimer()inonCreate()und den Aufruf vonstopTimer()inonStop(). Sie müssenDessertTimernicht mehr mitteilen, was in der Aktivität zu tun ist, daDessertTimerden Lebenszyklus jetzt selbst beobachtet und automatisch benachrichtigt wird, wenn sich der Lebenszyklusstatus ändert. In diesen Rückrufen wird jetzt nur noch eine Nachricht protokolliert. - Kompilieren Sie die App, führen Sie sie aus und öffnen Sie Logcat. Der Timer hat wie erwartet mit dem Countdown begonnen.

- Klicke auf die Startbildschirmtaste, um die App in den Hintergrund zu verschieben. Der Timer wurde wie erwartet gestoppt.
Was passiert mit meiner App und ihren Daten, wenn Android die App im Hintergrund schließt? Dieser schwierige Grenzfall ist wichtig.
Wenn Ihre App in den Hintergrund verschoben wird, wird sie nicht beendet, sondern nur angehalten und wartet darauf, dass der Nutzer zu ihr zurückkehrt. Eines der Hauptanliegen des Android-Betriebssystems ist es jedoch, die im Vordergrund ausgeführte Aktivität reibungslos laufen zu lassen. Wenn ein Nutzer beispielsweise eine GPS-App verwendet, um einen Bus zu erreichen, ist es wichtig, dass die GPS-App schnell gerendert wird und die Wegbeschreibung weiterhin angezeigt wird. Es ist weniger wichtig, dass die DessertClicker-App, die der Nutzer möglicherweise seit einigen Tagen nicht mehr verwendet hat, reibungslos im Hintergrund ausgeführt wird.
Android reguliert Hintergrund-Apps, damit die Vordergrund-App problemlos ausgeführt werden kann. Android begrenzt beispielsweise die Menge an Verarbeitung, die von Apps im Hintergrund ausgeführt werden kann.
Manchmal beendet Android sogar einen gesamten App-Prozess, einschließlich aller mit der App verknüpften Aktivitäten. Android führt diese Art von Beendigung durch, wenn das System überlastet ist und die Gefahr besteht, dass es zu visuellen Verzögerungen kommt. Zu diesem Zeitpunkt werden keine zusätzlichen Callbacks oder kein zusätzlicher Code ausgeführt. Der Prozess Ihrer App wird einfach im Hintergrund beendet. Für den Nutzer sieht es aber nicht so aus, als ob die App geschlossen wurde. Wenn der Nutzer zu einer App zurückkehrt, die vom Android-Betriebssystem geschlossen wurde, wird sie von Android neu gestartet.
In dieser Aufgabe simulieren Sie das Herunterfahren eines Android-Prozesses und sehen sich an, was mit Ihrer App passiert, wenn sie neu gestartet wird.
Schritt 1: Prozessbeendigung mit adb simulieren
Die Android Debug Bridge (adb) ist ein Befehlszeilentool, mit dem Sie Anweisungen an Emulatoren und Geräte senden können, die an Ihren Computer angeschlossen sind. In diesem Schritt verwenden Sie adb, um den Prozess Ihrer App zu schließen und zu sehen, was passiert, wenn Android Ihre App beendet.
- Kompilieren Sie Ihre App und führen Sie sie aus. Klicken Sie einige Male auf das Cupcake.
- Drücke die Startbildschirmtaste, um deine App in den Hintergrund zu verschieben. Ihre App wird jetzt beendet und kann geschlossen werden, wenn Android die von der App verwendeten Ressourcen benötigt.
- Klicken Sie in Android Studio auf den Tab Terminal, um das Befehlszeilenterminal zu öffnen.

- Geben Sie
adbein und drücken Sie die Eingabetaste.
Wenn Sie eine lange Ausgabe sehen, die mitAndroid Debug Bridge version X.XX.Xbeginnt und mittags to be used by logcat (see logcat —help) endet, ist alles in Ordnung. Wenn stattdessenadb: command not foundangezeigt wird, prüfen Sie, ob der Befehladbin Ihrem Ausführungspfad verfügbar ist. Eine Anleitung finden Sie im Kapitel Dienstprogramme unter „adb zum Ausführungspfad hinzufügen“. - Kopieren Sie diesen Kommentar und fügen Sie ihn in die Befehlszeile ein. Drücken Sie dann die Eingabetaste:
adb shell am kill com.example.android.dessertclickerMit diesem Befehl werden alle verbundenen Geräte oder Emulatoren angewiesen, den Prozess mit dem Paketnamen dessertclicker zu beenden, aber nur, wenn die App im Hintergrund ausgeführt wird. Da sich Ihre App im Hintergrund befand, wird auf dem Geräte- oder Emulatorbildschirm nichts angezeigt, was darauf hinweist, dass Ihr Prozess beendet wurde. Klicken Sie in Android Studio auf den Tab Run, um die Meldung „Application terminated“ (Anwendung beendet) zu sehen. Klicken Sie auf den Tab Logcat, um zu sehen, dass der onDestroy()-Callback nie ausgeführt wurde. Ihre Aktivität wurde einfach beendet.
- Über den Bildschirm „Zuletzt verwendet“ können Sie zur App zurückkehren. Ihre App wird dort angezeigt, unabhängig davon, ob sie in den Hintergrund verschoben oder vollständig beendet wurde. Wenn Sie über den Bildschirm „Letzte Apps“ zur App zurückkehren, wird die Aktivität neu gestartet. Die Aktivität durchläuft den gesamten Satz von Start-up-Lifecycle-Callbacks, einschließlich
onCreate(). - Beachten Sie, dass beim Neustart der App Ihr „Score“ (sowohl die Anzahl der verkauften Desserts als auch der Gesamtumsatz) auf die Standardwerte (0) zurückgesetzt wird. Wenn Android meine App geschlossen hat, warum wurde der Status nicht gespeichert?
Wenn das Betriebssystem Ihre App neu startet, versucht Android, den Status wiederherzustellen, den die App zuvor hatte. Android speichert den Status einiger Ihrer Ansichten in einem Bundle, wenn Sie die Aktivität verlassen. Einige Beispiele für Daten, die automatisch gespeichert werden, sind der Text in einem EditText-Feld (sofern im Layout eine ID festgelegt ist) und der Backstack Ihrer Aktivität.
Manchmal kennt das Android-Betriebssystem jedoch nicht alle Ihre Daten. Wenn Sie beispielsweise eine benutzerdefinierte Variable wierevenuein der DessertClicker-App haben, kennt das Android-Betriebssystem diese Daten oder ihre Bedeutung für Ihre Aktivität nicht. Sie müssen diese Daten selbst in das Bundle einfügen.
Schritt 2: Bundle-Daten mit onSaveInstanceState() speichern
Die Methode onSaveInstanceState() ist der Callback, mit dem Sie alle Daten speichern, die Sie möglicherweise benötigen, wenn das Android-Betriebssystem Ihre App beendet. Im Diagramm für den Lebenszyklus-Callback wird onSaveInstanceState() aufgerufen, nachdem die Aktivität beendet wurde. Wird jedes Mal aufgerufen, wenn Ihre App in den Hintergrund versetzt wird.

Der onSaveInstanceState()-Aufruf ist als Sicherheitsmaßnahme gedacht. Er gibt Ihnen die Möglichkeit, eine kleine Menge an Informationen in einem Bundle zu speichern, wenn Ihre Aktivität aus dem Vordergrund wechselt. Das System speichert diese Daten jetzt, da das Betriebssystem möglicherweise unter Ressourcenmangel leidet, wenn es bis zum Schließen der App wartet. Durch das Speichern der Daten wird sichergestellt, dass Aktualisierungsdaten im Bundle für die Wiederherstellung verfügbar sind, falls dies erforderlich ist.
- Überschreiben Sie in
MainActivitydenonSaveInstanceState()-Callback und fügen Sie eineTimber-Log-Anweisung hinzu.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.i("onSaveInstanceState Called")
}- Kompiliere und führe die App aus und klicke auf die Schaltfläche Startbildschirm, um sie in den Hintergrund zu verschieben. Der
onSaveInstanceState()-Callback erfolgt direkt nachonPause()undonStop():
- Fügen Sie oben in der Datei, direkt vor der Klassendefinition, diese Konstanten hinzu:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"Sie verwenden diese Schlüssel sowohl zum Speichern als auch zum Abrufen von Daten aus dem Instanzstatus-Bundle.
- Scrollen Sie nach unten zu
onSaveInstanceState()und beachten Sie den ParameteroutState, der vom TypBundleist.
Ein Bundle ist eine Sammlung von Schlüssel/Wert-Paaren, wobei die Schlüssel immer Strings sind. Sie können primitive Werte wieint- undboolean-Werte in das Bundle einfügen.
Da das System dieses Bundle im RAM behält, empfiehlt es sich, die Daten im Bundle klein zu halten. Die Größe dieses Bundles ist ebenfalls begrenzt, wobei die Größe je nach Gerät variiert. Im Allgemeinen sollten Sie weit weniger als 100.000 Elemente speichern, da Ihre App sonst mit dem FehlerTransactionTooLargeExceptionabstürzen kann. - Fügen Sie in
onSaveInstanceState()denrevenue-Wert (eine Ganzzahl) mit der MethodeputInt()in das Bundle ein:
outState.putInt(KEY_REVENUE, revenue)Die Methode putInt() (und ähnliche Methoden der Klasse Bundle wie putFloat() und putString()) verwendet zwei Argumente: einen String für den Schlüssel (die Konstante KEY_REVENUE) und den tatsächlichen Wert, der gespeichert werden soll.
- Wiederholen Sie den Vorgang mit der Anzahl der verkauften Desserts und dem Status des Timers:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)Schritt 3: Bundle-Daten mit onCreate() wiederherstellen
- Scrollen Sie nach oben zu
onCreate()und sehen Sie sich die Methodensignatur an:
override fun onCreate(savedInstanceState: Bundle) {Beachten Sie, dass onCreate() bei jedem Aufruf ein Bundle erhält. Wenn Ihre Aktivität aufgrund eines Prozess-Shutdowns neu gestartet wird, wird das gespeicherte Bundle an onCreate() übergeben. Wenn Ihre Aktivität neu begonnen hat, ist dieses Paket in onCreate() null. Wenn das Bündel also nicht null ist, wird die Aktivität ab einem zuvor bekannten Punkt „neu erstellt“.
- Fügen Sie diesen Code nach der Einrichtung von
DessertTimerzuonCreate()hinzu:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}Mit dem Test für null wird ermittelt, ob Daten im Bundle vorhanden sind oder ob das Bundle null ist. Daraus lässt sich wiederum ableiten, ob die App neu gestartet wurde oder nach dem Herunterfahren neu erstellt wurde. Dieser Test ist ein gängiges Muster zum Wiederherstellen von Daten aus dem Bundle.
Beachten Sie, dass der hier verwendete Schlüssel (KEY_REVENUE) derselbe ist wie der für putInt(). Damit Sie immer denselben Schlüssel verwenden, empfiehlt es sich, diese Schlüssel als Konstanten zu definieren. Mit getInt() rufen Sie Daten aus dem Bundle ab, so wie Sie mit putInt() Daten in das Bundle eingefügt haben. Die Methode getInt() verwendet zwei Argumente:
- Ein String, der als Schlüssel dient, z. B.
"key_revenue"für den Umsatzwert. - Ein Standardwert, falls für diesen Schlüssel im Bundle kein Wert vorhanden ist.
Die Ganzzahl, die Sie aus dem Bundle erhalten, wird dann der Variablen revenue zugewiesen und die Benutzeroberfläche verwendet diesen Wert.
- Fügen Sie
getInt()-Methoden hinzu, um die Anzahl der verkauften Desserts und den Wert des Timers wiederherzustellen:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}- Kompilieren Sie die App und führen Sie sie aus. Drücken Sie mindestens fünfmal auf das Cupcake-Symbol, bis es sich in ein Donut-Symbol ändert. Klicken Sie auf „Home“, um die App in den Hintergrund zu verschieben.
- Führen Sie auf dem Tab Terminal in Android Studio
adbaus, um den Prozess der App zu beenden.
adb shell am kill com.example.android.dessertclicker- Kehren Sie über den Bildschirm „Zuletzt verwendet“ zur App zurück. Dieses Mal werden die richtigen Umsatz- und Dessertwerte aus dem Bundle zurückgegeben. Das Dessert ist aber wieder ein Cupcake. Es gibt noch eine Sache, die Sie tun müssen, damit die App nach dem Herunterfahren genau so wiederhergestellt wird, wie sie verlassen wurde.
- Sehen Sie sich in
MainActivitydie MethodeshowCurrentDessert()an. Bei dieser Methode wird anhand der aktuellen Anzahl der verkauften Desserts und der Liste der Desserts in der VariablenallDessertsbestimmt, welches Dessertbild in der Aktivität angezeigt werden soll.
for (dessert in allDesserts) {
if (dessertsSold >= dessert.startProductionAmount) {
newDessert = dessert
}
else break
}Bei dieser Methode wird das richtige Bild anhand der Anzahl der verkauften Desserts ausgewählt. Daher müssen Sie nichts unternehmen, um einen Verweis auf das Bild im Bundle in onSaveInstanceState() zu speichern. In diesem Bundle speichern Sie bereits die Anzahl der verkauften Desserts.
- Rufen Sie in
onCreate()im Block, der den Status aus dem Bundle wiederherstellt,showCurrentDessert()auf:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
showCurrentDessert()
}- Kompilieren Sie die App, führen Sie sie aus und minimieren Sie sie. Verwenden Sie
adb, um den Vorgang zu beenden. Kehren Sie über den Bildschirm „Letzte Apps“ zur App zurück. Die Werte für „Desserts told“, „Total revenue“ und das Dessertbild werden jetzt korrekt wiederhergestellt.
Es gibt einen letzten Sonderfall bei der Verwaltung des Aktivitäts- und Fragmentlebenszyklus, den Sie kennen sollten: die Auswirkungen von Konfigurationsänderungen auf den Lebenszyklus Ihrer Aktivitäten und Fragmente.
Eine Konfigurationsänderung tritt ein, wenn sich der Status des Geräts so radikal ändert, dass das System die Änderung am einfachsten durch vollständiges Herunterfahren und Neuerstellen der Aktivität beheben kann. Wenn der Nutzer beispielsweise die Gerätesprache ändert, muss möglicherweise das gesamte Layout angepasst werden, um unterschiedliche Textrichtungen zu berücksichtigen. Wenn der Nutzer das Gerät an ein Dock anschließt oder eine physische Tastatur hinzufügt, muss das App-Layout möglicherweise eine andere Displaygröße oder ein anderes Layout nutzen. Wenn sich die Ausrichtung des Geräts ändert, z. B. wenn das Gerät vom Hoch- ins Querformat oder umgekehrt gedreht wird, muss sich das Layout möglicherweise an die neue Ausrichtung anpassen.
Schritt 1: Geräteausrichtung und Lifecycle-Callbacks kennenlernen
- Kompilieren und starten Sie Ihre App und öffnen Sie Logcat.
- Drehen Sie das Gerät oder den Emulator ins Querformat. Sie können den Emulator mit den Drehungstasten oder mit den
Control- und Pfeiltasten (Command- und Pfeiltasten auf einem Mac) nach links oder rechts drehen.
- Sehen Sie sich die Ausgabe in Logcat an. Filtern Sie die Ausgabe nach
MainActivity.
Wenn das Gerät oder der Emulator den Bildschirm dreht, ruft das System alle Lebenszyklus-Callbacks auf, um die Aktivität zu beenden. Wenn die Aktivität neu erstellt wird, ruft das System alle Lebenszyklus-Callbacks auf, um die Aktivität zu starten. - Kommentieren Sie in
MainActivitydie gesamte MethodeonSaveInstanceState()aus. - Kompilieren Sie Ihre App noch einmal und führen Sie sie aus. Klicken Sie mehrmals auf das Cupcake und drehen Sie das Gerät oder den Emulator. Wenn das Gerät gedreht und die Aktivität beendet und neu erstellt wird, wird die Aktivität dieses Mal mit Standardwerten gestartet.
Wenn eine Konfigurationsänderung erfolgt, verwendet Android dasselbe Instanzstatus-Bundle, das Sie im vorherigen Schritt kennengelernt haben, um den Status der App zu speichern und wiederherzustellen. Wie bei einem Prozess-Shutdown verwenden SieonSaveInstanceState(), um die Daten Ihrer App in das Bundle einzufügen. Stellen Sie dann die Daten inonCreate()wieder her, um zu vermeiden, dass Daten zum Aktivitätsstatus verloren gehen, wenn das Gerät gedreht wird. - Entfernen Sie in
MainActivitydie Auskommentierung deronSaveInstanceState()-Methode, führen Sie die App aus, klicken Sie auf das Cupcake und drehen Sie die App oder das Gerät. Dieses Mal bleiben die Dessertdaten auch nach dem Rotieren der Aktivität erhalten.
Android Studio-Projekt: DessertClickerFinal
Tipps zum Lebenszyklus
- Wenn Sie etwas in einem Lebenszyklus-Callback einrichten oder starten, müssen Sie es im entsprechenden Callback beenden oder entfernen. Wenn Sie das Gerät stoppen, wird es nicht weiter ausgeführt, wenn es nicht mehr benötigt wird. Wenn Sie beispielsweise einen Timer in
onStart()einrichten, müssen Sie ihn inonStop()pausieren oder stoppen. - Verwenden Sie
onCreate()nur, um die Teile Ihrer App zu initialisieren, die beim ersten Start der App einmal ausgeführt werden. Verwenden SieonStart(), um die Teile Ihrer App zu starten, die sowohl beim Start der App als auch jedes Mal, wenn die App in den Vordergrund zurückkehrt, ausgeführt werden.
Lifecycle-Bibliothek
- Mit der Android-Lifecycle-Bibliothek können Sie die Lebenszyklussteuerung von der Aktivität oder dem Fragment auf die tatsächliche Komponente verlagern, die lebenszyklusbewusst sein muss.
- Lebenszyklus-Inhaber sind Komponenten, die Lebenszyklen haben (und daher „besitzen“), einschließlich
ActivityundFragment. Lebenszyklusinhaber implementieren dieLifecycleOwner-Schnittstelle. - Beobachter für den Lebenszyklus achten auf den aktuellen Lebenszyklusstatus und führen Aufgaben aus, wenn sich der Lebenszyklus ändert. Lifecycle-Beobachter implementieren die
LifecycleObserver-Schnittstelle. Lifecycle-Objekte enthalten die tatsächlichen Lebenszyklusstatus und lösen Ereignisse aus, wenn sich der Lebenszyklus ändert.
So erstellen Sie eine lebenszyklusbewusste Klasse:
- Implementieren Sie die
LifecycleObserver-Schnittstelle in Klassen, die den Lebenszyklus berücksichtigen müssen. - Initialisieren Sie eine Lifecycle-Observer-Klasse mit dem Lifecycle-Objekt aus der Aktivität oder dem Fragment.
- Kennzeichnen Sie in der Klasse des Lebenszyklus-Observers Methoden, die den Lebenszyklus berücksichtigen, mit der Änderung des Lebenszyklusstatus, die sie beobachten.
Die Annotation@OnLifecycleEvent(Lifecycle.Event.ON_START)gibt beispielsweise an, dass die Methode das LebenszyklusereignisonStartbeobachtet.
Prozessbeendigungen und Speichern des Aktivitätsstatus
- Android regelt Apps, die im Hintergrund ausgeführt werden, damit die Vordergrund-App problemlos ausgeführt werden kann. Diese Verordnung umfasst die Begrenzung der Verarbeitung, die Apps im Hintergrund durchführen können, und manchmal sogar das Beenden des gesamten App-Prozesses.
- Der Nutzer kann nicht erkennen, ob das System eine App im Hintergrund geschlossen hat. Die App wird weiterhin auf dem Bildschirm mit den zuletzt verwendeten Apps angezeigt und sollte im selben Zustand neu gestartet werden, in dem der Nutzer sie verlassen hat.
- Die Android Debug Bridge (
adb) ist ein Befehlszeilentool, mit dem Sie Anweisungen an Emulatoren und Geräte senden können, die an Ihren Computer angeschlossen sind. Mitadbkönnen Sie das Herunterfahren eines Prozesses in Ihrer App simulieren. - Wenn Android den Prozess Ihrer App beendet, wird die
onDestroy()-Lebenszyklusmethode nicht aufgerufen. Die App wird einfach beendet.
Aktivitäts- und Fragmentstatus beibehalten
- Wenn Ihre App in den Hintergrund versetzt wird, werden die App-Daten kurz nach dem Aufrufen von
onStop()in einem Bundle gespeichert. Einige App-Daten, z. B. der Inhalt einesEditText, werden automatisch für Sie gespeichert. - Das Bundle ist eine Instanz von
Bundle, einer Sammlung von Schlüsseln und Werten. Die Schlüssel sind immer Strings. - Verwenden Sie den
onSaveInstanceState()-Callback, um andere Daten im Bundle zu speichern, die Sie behalten möchten, auch wenn die App automatisch geschlossen wurde. Verwenden Sie die Bundle-Methoden, die mitputbeginnen, z. B.putInt(), um Daten in das Bundle einzufügen. - Sie können Daten in der
onRestoreInstanceState()-Methode oder häufiger inonCreate()aus dem Bundle abrufen. Die MethodeonCreate()hat einen ParametersavedInstanceState, der das Bundle enthält. - Wenn die Variable
savedInstanceStatenullenthält, wurde die Aktivität ohne ein Status-Bundle gestartet und es sind keine Statusdaten abzurufen. - Wenn Sie Daten aus dem Bundle mit einem Schlüssel abrufen möchten, verwenden Sie die
Bundle-Methoden, die mitgetbeginnen, z. B.getInt().
Konfigurationsänderungen
- Eine Konfigurationsänderung tritt ein, wenn sich der Status des Geräts so radikal ändert, dass das System die Änderung am einfachsten durch Herunterfahren und Neuerstellen der Aktivität beheben kann.
- Das häufigste Beispiel für eine Konfigurationsänderung ist, wenn der Nutzer das Gerät vom Hoch- ins Querformat oder vom Quer- ins Hochformat dreht. Eine Konfigurationsänderung kann auch auftreten, wenn sich die Gerätesprache ändert oder eine Hardwaretastatur angeschlossen wird.
- Wenn eine Konfigurationsänderung eintritt, ruft Android alle Shutdown-Callbacks des Aktivitätslebenszyklus auf. Android startet die Aktivität dann von Grund auf neu und führt alle Lifecycle-Start-Callbacks aus.
- Wenn Android eine App aufgrund einer Konfigurationsänderung schließt, wird die Aktivität mit dem Status-Bundle neu gestartet, das für
onCreate()verfügbar ist. - Wie beim Beenden des Prozesses müssen Sie den Zustand Ihrer App im Bundle in
onSaveInstanceState()speichern.
Udacity-Kurs:
Android-Entwicklerdokumentation:
- Aktivitäten (API-Leitfaden)
Activity(API-Referenz)- Informationen zum Aktivitätslebenszyklus
- Umgang mit Lebenszyklen mit lebenszyklusbewussten Komponenten
LifecycleOwnerLifecycleLifecycleObserveronSaveInstanceState()- Umgang mit Konfigurationsänderungen
- UI-Zustände speichern
Sonstiges:
- Timber (GitHub)
In diesem Abschnitt werden mögliche Hausaufgaben für Schüler und Studenten aufgeführt, die dieses Codelab im Rahmen eines von einem Kursleiter geleiteten Kurses durcharbeiten. Es liegt in der Verantwortung des Kursleiters, Folgendes zu tun:
- Weisen Sie bei Bedarf Aufgaben zu.
- Teilen Sie den Schülern/Studenten mit, wie sie Hausaufgaben abgeben können.
- Benoten Sie die Hausaufgaben.
Lehrkräfte können diese Vorschläge nach Belieben nutzen und auch andere Hausaufgaben zuweisen, die sie für angemessen halten.
Wenn Sie dieses Codelab selbst durcharbeiten, können Sie mit diesen Hausaufgaben Ihr Wissen testen.
App ändern
Öffnen Sie die DiceRoller-App aus Lektion 1. Wenn Sie die App noch nicht haben, können Sie sie hier herunterladen. Kompilieren Sie die App und führen Sie sie aus. Wenn Sie das Gerät drehen, geht der aktuelle Wert des Würfels verloren. Implementieren Sie onSaveInstanceState(), um diesen Wert im Bundle beizubehalten und in onCreate() wiederherzustellen.
Beantworten Sie diese Fragen
Frage 1
Ihre App enthält eine Physiksimulation, für deren Darstellung umfangreiche Berechnungen erforderlich sind. Der Nutzer wird dann angerufen. Welche der folgenden Aussagen ist richtig?
- Während des Telefonats sollten Sie die Positionen von Objekten in der Physiksimulation weiter berechnen.
- Während des Anrufs sollten Sie die Berechnung der Positionen von Objekten in der Physiksimulation beenden.
Frage 2
Welche Lebenszyklusmethode sollten Sie überschreiben, um die Simulation zu pausieren, wenn die App nicht auf dem Bildschirm angezeigt wird?
onDestroy()onStop()onPause()onSaveInstanceState()
Frage 3
Welche Schnittstelle muss eine Klasse implementieren, damit sie durch die Android-Lebenszyklusbibliothek lebenszyklusbewusst wird?
LifecycleLifecycleOwnerLifecycle.EventLifecycleObserver
Frage 4
Unter welchen Umständen erhält die Methode onCreate() in Ihrer Aktivität ein Bundle mit Daten (d. h. das Bundle ist nicht null)? Es kann mehr als eine Antwort zutreffen.
- Die Aktivität wird neu gestartet, nachdem das Gerät gedreht wurde.
- Die Aktivität wird von Grund auf neu gestartet.
- Die Aktivität wird fortgesetzt, wenn die App aus dem Hintergrund zurückkehrt.
- Das Gerät wird neu gestartet.
Nächste Lektion starten:
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Android Kotlin Fundamentals-Codelabs.