Android Kotlin Fundamentals 04.2: Komplexe Lebenszyklussituationen

Dieses Codelab ist Teil des Android Kotlin Fundamentals-Kurss. Sie profitieren von diesem Kurs, wenn Sie nacheinander die Codelabs durcharbeiten. Alle Kurs-Codelabs finden Sie auf der Landingpage für Kotlin-Grundlagen für Android-Entwickler.

Einführung

Im letzten Codelab haben Sie den Lebenszyklus von Activity und Fragment gelernt und sich mit den Methoden vertraut gemacht, die aufgerufen werden, wenn sich der Lebenszyklusstatus in Aktivitäten und Fragmenten ändert. In diesem Codelab untersuchen Sie den Aktivitätslebenszyklus genauer. Außerdem lernst du die Lebenszyklusbibliothek von Android Jetpack kennen, die dir helfen kann, Lebenszyklusereignisse mit Code zu verwalten, der besser organisiert und einfacher zu verwalten ist.

Wichtige Informationen

  • Was ist eine Aktivität und wie wird sie in Ihrer App erstellt?
  • Die Grundlagen des Activity- und Fragment-Lebenszyklus und die Callbacks, die aufgerufen werden, wenn eine Aktivität zwischen den Status wechselt.
  • Die Callback-Methoden onCreate() und onStop() können überschrieben werden, um Vorgänge zu verschiedenen Zeiten im Aktivitäts- oder Fragmentlebenszyklus auszuführen.

Lerninhalte

  • Bestandteile der App in den Lebenszyklus-Callbacks einrichten, starten und stoppen
  • Mit der Android-Lebenszyklus-Bibliothek einen Lebenszyklus-Beobachter erstellen und die Verwaltung des Aktivitäts- und Fragmentzyklus vereinfachen
  • Wie sich das Herunterfahren des Android-Prozesses auf die Daten in deiner App auswirkt und wie du diese Daten speichern und wiederherstellen kannst, wenn Android deine App schließt
  • Wie durch Geräterotation und andere Konfigurationsänderungen Änderungen am Lebenszykluszustand vorgenommen werden und diese sich auf den Status deiner App auswirken

Aufgabe

  • Du kannst die DessertClicker-App so anpassen, dass eine Timer-Funktion eingebunden wird, und diesen Timer zu verschiedenen Zeiten im Aktivitätslebenszyklus starten und beenden.
  • Ändern Sie die App so, dass die Android-Lebenszyklus-Bibliothek verwendet wird, und konvertieren Sie die DessertTimer-Klasse in einen Lebenszyklusbeobachter.
  • Richten Sie die Android Debug Bridge (adb) ein und simulieren Sie das Herunterfahren Ihrer App und die zugehörigen Lebenszyklus-Callbacks.
  • Implementiere 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 beim Neustart der App wiederherzustellen.

In diesem Codelab weiten Sie die DessertClicker-App aus dem vorherigen Codelab aus. Sie fügen einen Hintergrund-Timer hinzu und konvertieren die App dann in die Lebenszyklus-Bibliothek von Android.

Im vorherigen Codelab haben Sie gelernt, wie Sie die Aktivitäten und Fragment-Lebenszyklus beobachten, indem Sie verschiedene Lebenszyklus-Callbacks überschreiben. Außerdem wird beschrieben, wie das System diese Callbacks aufruft. In dieser Aufgabe sehen Sie ein komplexeres Beispiel für die Verwaltung von Lebenszyklusaufgaben in der DessertClicker-App. Sie verwenden einen Timer, der jede Sekunde eine Protokollanweisung mit der Anzahl der Sekunden druckt, die bereits ausgeführt wurden.

Schritt 1: DessertTimer einrichten

  1. Öffne die DessertClicker-App aus dem letzten Codelab. Du kannst DessertClickerLogs hier herunterladen, wenn du die App nicht hast.
  2. Maximieren Sie in der Projektansicht j > com.example.android.esseclicker und öffnen Sie DessertTimer.kt. Hinweis: Der gesamte Code ist auskommentiert und wird deshalb nicht als Teil der App ausgeführt.
  3. Wählen Sie den gesamten Code im Editorfenster aus. Wählen Sie Code > Kommentar mit Zeilenkommentar aus oder drücken Sie Control+/ (Command+/ auf einem Mac). Mit diesem Befehl wird der gesamte Code in der Datei aus der Datei entfernt. In Android Studio werden möglicherweise ungelöste Referenzfehler angezeigt, bis Sie die App neu erstellen.
  4. Die Klasse DessertTimer enthält startTimer() und stopTimer(), die den Timer starten und stoppen. Wenn startTimer() ausgeführt wird, gibt der Timer pro Sekunde eine Protokollnachricht aus, wobei die Gesamtzahl der Sekunden angegeben wird, zu denen die Zeit vergangen ist. Die Methode stopTimer() beendet wiederum den Timer und die Loganweisungen.
  1. Öffnen Sie MainActivity.kt. Fügen Sie oben im Kurs, direkt unter der Variable dessertsSold, eine Variable für den Timer ein:
private lateinit var dessertTimer : DessertTimer;
  1. Scrollen Sie nach unten zu onCreate() und erstellen Sie ein neues DessertTimer-Objekt, direkt nach dem Aufruf von setOnClickListener():
dessertTimer = DessertTimer()


Sie haben jetzt einen Timer für das Dessertobjekt aktiviert. Überlegen Sie, wo Sie den Timer starten und anhalten möchten, damit er nur dann ausgeführt wird, wenn die Aktivität auf dem Bildschirm erscheint. Im nächsten Schritt sehen Sie sich einige Optionen an.

Schritt 2: Timer starten und stoppen

Die Methode onStart() wird direkt vor der Sichtbarkeit der Aktivität aufgerufen. Die Methode onStop() wird aufgerufen, nachdem die Aktivität nicht mehr sichtbar ist. Diese Callbacks sind gute Kandidaten für den Start und Stopps von Timern.

  1. Starten Sie im MainActivity-Kurs den Timer im onStart()-Callback:
    .
override fun onStart() {
   super.onStart()
   dessertTimer.startTimer()

   Timber.i("onStart called")
}
  1. Timer in onStop() beenden:
override fun onStop() {
   super.onStop()
   dessertTimer.stopTimer()

   Timber.i("onStop Called")
}
  1. Kompilieren und führen Sie die App aus. Klicken Sie in Android Studio auf den Bereich Logcat. Geben Sie im Logcat-Suchfeld dessertclicker ein. Das Filtern erfolgt sowohl nach MainActivity als auch nach DessertTimer. Nach dem Start der App beginnt auch der Timer sofort.
  2. Wenn Sie auf die Schaltfläche Zurück klicken, wird der Timer gestoppt. Der Timer stoppt, weil die Aktivität und der Timer, den er gesteuert hat, gelöscht wurden.
  3. Verwenden Sie den Bildschirm "Zuletzt verwendet", um zur App zurückzukehren. Beachten Sie in Logcat, dass der Timer bei 0 neu startet.
  4. Klicken Sie auf die Schaltfläche Teilen. Sieh in Logcat, dass der Timer noch läuft.

  5. Klicken Sie auf die Schaltfläche Startseite. Beachten Sie in Logcat, dass der Timer nicht mehr läuft.
  6. Tippen Sie auf den Bildschirm "Zuletzt verwendet", um zur App zurückzukehren. Beachten Sie in Logcat, dass der Timer an der Stelle fortgesetzt wird, an der er angehalten wurde.
  7. Kommentieren Sie in MainActivity in der Methode onStop() den Aufruf an stopTimer(). Durch das Kommentieren von stopTimer() wird der Fall veranschaulicht, in dem ein Vorgang in onStart() gestartet wird. Vergessen Sie jedoch nicht, ihn in onStop() noch einmal zu beenden.
  8. Kompilieren und führen Sie die App aus und klicken Sie nach dem Start des Timers auf die Startbildschirmtaste. Obwohl die App im Hintergrund ausgeführt wird, läuft der Timer ständig und es werden weiterhin Systemressourcen genutzt. Wenn der Timer weiter ausgeführt wird, bedeutet das ein Speicherleck für Ihre App, was wahrscheinlich nicht das gewünschte Verhalten ist.

    Ganz allgemein besteht die Gefahr, dass beim Einrichten oder Starten eines Callbacks eine Aktion durch das entsprechende Callback beendet oder entfernt wird. So vermeiden Sie, dass alles ausgeführt wird, wenn es nicht mehr benötigt wird.
  1. Entfernen Sie die Kommentarzeile in onStop(), wo Sie den Timer stoppen.
  2. Schneiden Sie den startTimer()-Aufruf aus und fügen Sie ihn von onStart() in onCreate() ein. Diese Änderung veranschaulicht, dass eine Ressource in onCreate() initialisiert und gestartet wird und nicht mit onCreate() initialisiert und mit onStart() gestartet wird.
  3. Kompilieren und führen Sie die App aus. Der Timer läuft wie erwartet.
  4. Klicke auf "Startseite", um die App zu beenden. Der Timer funktioniert wie erwartet.
  5. Über den Bildschirm „Kürzlich verwendet“ gelangst du zur App zurück. Der Timer startet in diesem Fall nicht neu, da onCreate() nur aufgerufen wird, wenn die App startet, nicht jedoch, wenn eine App wieder in den Vordergrund rückt.

Wichtige Hinweise:

  • Wenn Sie eine Ressource in einem Lebenszyklus-Callback einrichten, löschen Sie auch die Ressource.
  • Führen Sie die Einrichtung und das Entfernen in den entsprechenden Methoden durch.
  • Wenn Sie eine Datei in onStart() einrichten, beenden oder löschen Sie sie in onStop().

In der DessertClicker-App ist es ziemlich einfach, den Timer in onStop() zu stoppen, wenn du ihn gestartet hast. Es gibt nur einen Timer. Du kannst ihn dir also nicht merken.

In einer komplexeren Android-App kannst du viele Dinge in onStart() oder onCreate() einrichten und dann in onStop() oder onDestroy() herunterfahren. Beispielsweise können Sie Animationen, Musik, Sensoren oder Timer einrichten und anhalten sowie starten und beenden. Das führt zu Fehlern und Kopfschmerzen.

Die Lebenszyklusbibliothek, die Teil von Android Jetpack ist, vereinfacht diese Aufgabe. Die Bibliothek ist besonders nützlich, wenn Sie viele bewegliche Teile erfassen müssen, von denen einige unterschiedliche Lebenszyklusstatus haben. Die Bibliothek kehrt um. Wenn Sie jedoch die Lebenszyklusbibliothek verwenden, berücksichtigt die Komponente selbst die Lebenszyklusänderungen und führt dann die notwendigen Schritte aus, wenn diese Änderungen erfolgen.

Die Lebenszyklusbibliothek besteht aus drei Hauptteilen:

  • Inhaber des Lebenszyklus Activity und Fragment sind Lebenszyklusinhaber. Lebenszyklusinhaber implementieren die LifecycleOwner-Schnittstelle.
  • Die Klasse Lifecycle, die den tatsächlichen Status eines Lebenszyklusinhabers enthält und Ereignisse bei Lebenszyklusänderungen auslöst.
  • Lebenszyklus Server, die den Lebenszyklusstatus beobachten und Aufgaben ausführen, wenn sich der Lebenszyklus ändert. Lebenszyklusbetrachter verwenden die Schnittstelle LifecycleObserver.

In dieser Aufgabe wandeln Sie die DessertClicker-App in die Android-Lebenszyklus-Bibliothek um. Außerdem erfahren Sie, wie Sie mithilfe der Bibliothek ihre Android-Aktivitäten und Fragment-Lebenszyklus einfacher verwalten können.

Schritt 1: DessertTimer in einen LebenszyklusObserver umwandeln

Ein wichtiger Teil der Lebenszyklusbibliothek ist das Lebenszykluskonzept. Mithilfe der Beobachtung können Kurse (z. B. DessertTimer) über den Lebenszyklus von Aktivitäten oder Fragmenten informiert werden. Außerdem haben sie die Möglichkeit, sich selbst als Reaktion auf Änderungen dieses Lebenszyklusstatus zu starten und zu stoppen. Wenn Sie einen Lebenszyklusbeobachter verwenden, können Sie die Verantwortung für das Starten und Beenden von Objekten aus den Aktivitäts- und Fragmentmethoden aufheben.

  1. Öffnen Sie den Kurs DesertTimer.kt.
  2. Ändern Sie die Kurssignatur der Klasse DessertTimer so:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

Diese neue Klassendefinition erfüllt zwei Dinge:

  • Der Konstruktor verwendet ein Lifecycle-Objekt. Das ist der Lebenszyklus, der vom Timer überwacht wird.
  • Die Klassendefinition implementiert die Schnittstelle LifecycleObserver.
  1. Fügen Sie unter der Variable runnable der Blockdefinition einen init-Block hinzu. Verwenden Sie im Block init die Methode addObserver(), um das Lebenszyklusobjekt, das vom Inhaber übergeben wurde (die Aktivität), mit dieser Klasse (dem Beobachter) zu verbinden.
 init {
   lifecycle.addObserver(this)
}
  1. Annotieren Sie startTimer() mit dem @OnLifecycleEvent annotation und verwenden Sie das Lebenszyklusereignis ON_START. Alle Lebenszyklusereignisse, die Ihr Lebenszyklusbeobachter beobachten kann, sind in der Klasse Lifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. Tun Sie dasselbe mit dem Ereignis ON_STOP mit stopTimer():
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

Schritt 2: Hauptaktivität ändern

Ihre MainActivity-Klasse ist bereits durch eine Übernahme ein Lebenszyklusinhaber, da mit der FragmentActivity-Klasse LifecycleOwner implementiert wird. Sie müssen nichts weiter tun, damit Ihre Aktivitäten in der Zeit des Lebenszyklus berücksichtigt werden. Du musst nur das Lebenszyklusobjekt in den DessertTimer-Konstruktor übergeben.

  1. Öffnen Sie MainActivity. Ändere in der onCreate()-Methode die Initialisierung von DessertTimer so, dass sie this.lifecycle enthält:
dessertTimer = DessertTimer(this.lifecycle)

Das Attribut lifecycle der Aktivität enthält das Lifecycle-Objekt, zu dem diese Aktivität gehört.

  1. Anruf für startTimer() in onCreate() und stopTimer() bei onStop() entfernen. Sie müssen DessertTimer nicht mehr mitteilen, welche Schritte die Aktivität ausführen soll. Das liegt daran, dass DessertTimer jetzt den Lebenszyklus selbst beobachtet und automatisch benachrichtigt wird, wenn sich der Lebenszyklusstatus ändert. Bei allen Callbacks wird jetzt eine Nachricht protokolliert.
  2. Kompilieren und führen Sie die App aus und öffnen Sie die Logcat. Der Timer läuft wie erwartet.
  3. Klicken Sie auf die Startbildschirmtaste, um die App in den Hintergrund zu verschieben. Der Timer läuft nicht mehr wie erwartet.

Was passiert mit Ihrer App und ihren Daten, wenn Android die App im Hintergrund herunterfährt? Der knifflige Grenzfall ist dabei wichtig.

Wird deine App in den Hintergrund verschoben, wird sie nicht gelöscht, sie wird nur angehalten und wartet, bis der Nutzer sie zurückgibt. Eines der Hauptanliegen von Android OS ist jedoch, dass die Aktivität, die im Vordergrund läuft, reibungslos funktioniert. Wenn ein Nutzer beispielsweise eine GPS-App verwendet, um einen Bus zu erkennen, ist es wichtig, dass die GPS-App schnell gerendert und eine Wegbeschreibung angezeigt wird. Außerdem ist es weniger wichtig, die DessertClicker-App beizubehalten, die der Nutzer einige Tage lang nicht angesehen hat, da sie reibungslos im Hintergrund läuft.

Android reguliert Hintergrund-Apps so, dass die App im Vordergrund ohne Probleme ausgeführt wird. Beispielsweise beschränkt Android die Verarbeitungsmenge, die Apps im Hintergrund ausführen können.

Es kann vorkommen, dass Android den gesamten App-Prozess herunterfährt, einschließlich aller Aktivitäten, die mit der App in Verbindung stehen. Android wird dabei jedoch heruntergefahren, wenn das System überlastet ist und es zu Verzögerungen kommt. Daher werden an diesem Punkt keine weiteren Rückrufe oder Code ausgeführt. Deine App wird im Hintergrund automatisch heruntergefahren. Für den Nutzer wurde die App jedoch nicht geschlossen. Wenn der Nutzer zu einer App zurückkehrt, die Android OS beendet hat, startet Android diese App neu.

In dieser Aufgabe simulieren Sie das Herunterfahren eines Android-Prozesses und prüfen, was mit Ihrer App passiert, wenn sie wieder gestartet wird.

Schritt 1: ADB verwenden, um das Herunterfahren eines Prozesses zu 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 angehängt sind. In diesem Schritt verwendest du adb, um deine App zu schließen und zu sehen, was passiert, wenn Android deine App herunterfährt.

  1. Kompiliere und führe deine App aus. Klicke mehrmals auf den Cupcake.
  2. Klicken Sie auf die Startbildschirmtaste, um Ihre App im Hintergrund auszuführen. Ihre App wird jetzt beendet und sie kann geschlossen werden, wenn Android die von ihr genutzten Ressourcen benötigt.
  3. Klicken Sie in Android Studio auf den Tab Terminal, um das Befehlszeilenterminal zu öffnen.
  4. Gib „adb“ ein und drücke die Eingabetaste.

    Wenn du eine große Ausgabe siehst, die mit „Android Debug Bridge version X.XX.X“ beginnt und mit tags to be used by logcat (see logcat —h„ Elp“ endet, ist alles in Ordnung. Wenn adb: command not found angezeigt wird, prüfen Sie, ob der Befehl adb im Ausführungspfad verfügbar ist. Eine Anleitung finden Sie im Kapitel zur Dienstprogramme unter „addb zu Ihrem Ausführungspfad hinzufügen“.
  5. Kopieren Sie diesen Kommentar in die Befehlszeile und drücken Sie die Eingabetaste:
adb shell am kill com.example.android.dessertclicker

Mit diesem Befehl werden verbundene Geräte oder Emulatoren angewiesen, den Prozess mit dem dessertclicker-Paketnamen zu beenden, aber nur, wenn die App im Hintergrund ausgeführt wird. Da Ihre App im Hintergrund ausgeführt wurde, wird auf dem Gerät oder dem Emulator nichts angezeigt, um anzuzeigen, dass der Prozess angehalten wurde. Klicken Sie in Android Studio auf den Tab Ausführen, um die Meldung „Anwendung beendet“ zu sehen. Klicken Sie auf den Tab Logcat, um zu sehen, ob der Callback onDestroy() nicht ausgeführt wurde – die Aktivität wurde einfach beendet.

  1. Auf dem Bildschirm „Zuletzt verwendet“ erscheint die App wieder. Die App wird unter „Neueste“ angezeigt, unabhängig davon, ob sie in den Hintergrund verschoben oder vollständig unterbrochen wurde. Wenn Sie den Bildschirm „Letzte“ verwenden, werden die Aktivitäten wieder gestartet. Die Aktivität durchläuft den gesamten Lebenszyklus-Callbacks des Start-ups, einschließlich onCreate().
  2. Beachten Sie, dass nach dem Neustart der App der Wert (d. h. die Anzahl der Desserts und der Gesamtumsatz) auf die Standardwerte (0) zurückgesetzt wird. Warum hat Android den Status Ihrer App nicht gespeichert, wenn er sie beendet hat?

    Wenn das Betriebssystem Ihre App neu startet, versucht Android, sie auf den vorherigen Zustand zurückzusetzen. Android speichert einige Zustandsdaten in einer Gruppierung, wenn Sie die Seite verlassen. Einige Beispiele für Daten, die automatisch gespeichert werden, sind der Text im Bearbeitungstext (solange eine ID im Layout festgelegt ist) und der Back-Stack Ihrer Aktivität.

    Aber manchmal kennt das Android-Betriebssystem nicht alle Daten. Wenn du beispielsweise eine benutzerdefinierte Variable wie revenue in der DessertClicker-App hast, weiß das Android-Betriebssystem nicht über diese Daten oder ihre Bedeutung für deine Aktivität. Sie müssen diese Daten dem Paket selbst hinzufügen.

Schritt 2: BundleFile-Daten mit onSaveInstanceState() speichern

Die Methode onSaveInstanceState() ist der Callback, mit dem alle Daten gespeichert werden, die Sie zum Löschen Ihrer App für Android OS benötigen. Im Lebenszyklus-Callback-Diagramm wird onSaveInstanceState() aufgerufen, nachdem die Aktivität beendet wurde. Sie wird jedes Mal aufgerufen, wenn deine App in den Hintergrund gestellt wird.

Stellen Sie sich den onSaveInstanceState()-Aufruf als Sicherheitsmaßnahme vor: Sie können eine kleine Menge an Informationen in einem Bundle speichern, wenn Ihre Aktivität den Vordergrund verlässt. Das System speichert diese Daten jetzt, da es sonst möglicherweise zu einer Belastung des Betriebssystems kommt, wenn es gewartet wird, bis die App heruntergefahren wurde. So können Sie bei Bedarf die Daten im Bundle wiederherstellen, falls erforderlich.

  1. Überschreibe in MainActivity den onSaveInstanceState()-Callback und füge eine Timber-Loganweisung hinzu.
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Timber.i("onSaveInstanceState Called")
}
  1. Kompilieren und führen Sie die App aus und klicken Sie auf die Schaltfläche Startseite, um sie im Hintergrund auszuführen. Beachten Sie, dass der onSaveInstanceState()-Callback direkt nach onPause() und onStop() stattfindet:
  2. 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 zum Speichern und Abrufen von Daten aus dem Instanzstatus-Bundle.

  1. Scrollen Sie nach unten zu onSaveInstanceState() und sehen Sie sich den Parameter outState an, der den Typ Bundle hat.

    Ein Bundle ist eine Sammlung von Schlüssel/Wert-Paaren, wobei die Schlüssel immer Strings sind. Sie können einfache Werte wie int- und boolean-Werte in das Bundle einfügen.
    Da das Set im RAM des Systems aufbewahrt wird, empfiehlt es sich, die Daten des Bundles klein zu halten. Die Größe dieses Sets ist ebenfalls begrenzt, die Größe variiert jedoch je nach Gerät. Im Allgemeinen solltest du weniger als 100.000 $ speichern, da die App sonst möglicherweise mit dem Fehler TransactionTooLargeException nicht abstürzt.
  2. Setzen Sie in onSaveInstanceState() den Wert revenue (eine Ganzzahl) mit der Methode putInt() in das Set:
outState.putInt(KEY_REVENUE, revenue)

Für die putInt()-Methode und ähnliche Methoden aus der Bundle-Klasse wie putFloat() und putString() werden zwei Argumente verwendet: ein String für den Schlüssel (die Konstante KEY_REVENUE) und der tatsächliche Wert, der gespeichert werden soll.

  1. Wiederholen Sie den Vorgang mit der Anzahl der Desserts und dem Timerstatus:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

Schritt 3: Bundle-Daten wiederherstellen

  1. Scrollen Sie nach oben zu onCreate() und prüfen Sie die Signatur der Methode:
override fun onCreate(savedInstanceState: Bundle) {

Beachten Sie, dass onCreate() jedes Mal einen Bundle erhält, wenn er aufgerufen wird. Wenn deine Aktivität aufgrund eines Prozesses des Prozesses neu gestartet wird, wird das gespeicherte Bundle an onCreate() übergeben. Wenn deine Aktivität gerade neu begann, ist dieses Set in onCreate() null. Wenn es sich bei dem Paket nicht um null handelt, wissen Sie, dass Sie die Aktivität von einem zuvor bekannten Punkt aus neu erstellen.

  1. Fügen Sie diesen Code nach der Einrichtung von DessertTimer in onCreate() hinzu:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

Beim Test für null wird ermittelt, ob das Bundle Daten enthält oder ob das Bundle null ist. So erfahren Sie, ob die App nach einem Herunterfahren neu gestartet oder neu erstellt wurde. Dieser Test ist ein gängiges Muster zum Wiederherstellen von Daten aus dem Bundle.

Der hier verwendete Schlüssel (KEY_REVENUE) ist derselbe, den Sie für putInt() verwendet haben. Damit Sie denselben Schlüssel jedes Mal verwenden, sollten Sie diese Schlüssel als Konstanten definieren. Mit getInt() können Sie Daten aus dem Bundle abrufen, genauso wie Sie mit putInt() Daten in das Bundle aufgenommen haben. Die Methode getInt() verwendet zwei Argumente:

  • Ein String, der als Schlüssel fungiert, z. B. "key_revenue" für den Umsatzwert.
  • Ein Standardwert, falls für diesen Schlüssel im Set kein Wert vorhanden ist.

Die Ganzzahl, die Sie vom Bundle erhalten, wird dann der Variable revenue zugewiesen. Die UI verwendet diesen Wert.

  1. Füge die Methoden getInt() hinzu, um die Anzahl der 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)
}
  1. Kompilieren und führen Sie die App aus. Drücken Sie den Cupcake mindestens fünfmal, bis er in einen Donut wechselt. Klicken Sie auf „Startseite“, um die App im Hintergrund auszuführen.
  2. Führe auf dem Android Studio-Tab Terminal adb aus, um die App herunterzufahren.
adb shell am kill com.example.android.dessertclicker
  1. Über den Bildschirm „Zuletzt verwendet“ kannst du zur App zurückkehren. Dieses Mal werden in der App die richtigen Umsatz- und Süßwarenwerte aus dem Paket zurückgegeben. Beachte aber auch, dass das Dessert zu einem Cupcake zurückgekehrt ist. Es gibt noch ein paar Schritte, um zu gewährleisten, dass die App nach dem Herunterfahren jetzt wieder wie vorgesehen heruntergefahren wird.
  2. Prüfe in MainActivity die Methode showCurrentDessert(). Hinweis: Mit dieser Methode wird bestimmt, welches Dessertbild in der Aktivität angezeigt werden soll. Das hängt von der aktuellen Anzahl der Desserts und der Liste der Desserts in der Variable allDesserts ab.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

Diese Methode basiert auf der Anzahl der verkauften Süßigkeiten, um das richtige Bild auszuwählen. Sie müssen daher nichts weiter unternehmen, um eine Referenz auf das Bild im Bundle in onSaveInstanceState() zu speichern. In diesem Set speicherst du bereits die Anzahl der Desserts.

  1. Rufen Sie in onCreate() in dem Block, der den Status aus dem Bundle wiederhergestellt hat, 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()                   
}
  1. Kompilieren und führen Sie die App aus und legen Sie sie im Hintergrund ab. Verwenden Sie adb, um den Vorgang zu beenden. Nutze die Ansicht „Zuletzt verwendet“, um zur App zurückzukehren. Beachte, dass sowohl die Werte für Desserts als auch der Gesamtumsatz und das Dessert-Bild korrekt wiederhergestellt werden.

Ein weiterer Sonderfall beim Verwalten des Aktivitäts- und Fragmentlebenszyklus, der wichtig zu verstehen ist: wie sich Konfigurationsänderungen auf den Lebenszyklus Ihrer Aktivitäten und Fragmente auswirken.

Eine Konfigurationsänderung findet statt, wenn der Status des Geräts radikal geändert wird. Dadurch ist es für das System am einfachsten, die Änderung einfach zu beheben und die Aktivität vollständig neu zu erstellen. Wenn ein Nutzer beispielsweise die Sprache des Geräts ändert, muss möglicherweise das gesamte Layout angepasst werden, um unterschiedliche Textrichtungen zu berücksichtigen. Wenn der Nutzer das Gerät an eine Dockingstation anschließt oder eine physische Tastatur hinzufügt, braucht das App-Layout möglicherweise eine andere Displaygröße oder ein anderes Layout. Sollte sich das Gerät jedoch im Hoch- oder Querformat befinden, ändert sich das Layout unter Umständen, damit es an die neue Ausrichtung angepasst wird.

Schritt 1: Geräterotation und Lebenszyklus-Callbacks kennenlernen

  1. Kompilieren und führen Sie Ihre App aus und öffnen Sie Logcat.
  2. Drehen Sie das Gerät oder den Emulator ins Querformat. Mit den Rotationsschaltflächen kannst du den Emulator nach links oder rechts drehen. Auf einem Mac kannst du die Control- und die Pfeiltasten (Command und die Pfeiltasten) verwenden.
  3. Prüfen Sie die Ausgabe in Logcat. Filtern Sie die Ausgabe im MainActivity.
    Hinweis: Wenn das Gerät oder der Emulator das Display dreht, werden alle Lebenszyklus-Callbacks aufgerufen, um die Aktivität herunterzufahren. Wenn die Aktivität neu erstellt wird, ruft das System alle Lebenszyklus-Callbacks auf, um die Aktivität zu starten.
  4. Kommentieren Sie in MainActivity die gesamte onSaveInstanceState()-Methode aus.
  5. Kompilieren und führen Sie Ihre App noch einmal aus. Klicken Sie mehrmals auf den Cupcake und drehen Sie das Gerät oder den Emulator. Dieses Mal, wenn das Gerät rotiert und die Aktivität heruntergefahren und neu erstellt wird, beginnt die Aktivität mit den Standardwerten.

    Wenn eine Konfigurationsänderung erfolgt, verwendet Android dasselbe Instanzstatus-Bundle, das du bei der vorherigen Aufgabe gelernt hast, um den Status der App zu speichern und wiederherzustellen. Verwende wie bei einem Prozessabschaltung onSaveInstanceState(), um deine App-Daten in das Bundle aufzunehmen. Stellen Sie dann die Daten in onCreate() wieder her. So können Sie verhindern, dass Daten zum Aktivitätsstatus verloren gehen, wenn das Gerät gedreht wird.
  6. Entfernen Sie in MainActivity die Kommentarzeichen für die Methode onSaveInstanceState(), führen Sie die App aus, klicken Sie auf den Cupcake und drehen Sie die App oder das Gerät. Die Dessertdaten werden diesmal bei der Aktivitätsrotation gespeichert.

Android Studio-Projekt: DessertClickerFinal

Lebenszyklus-Tipps

  • Wenn Sie etwas in einem Lebenszyklus-Callback einrichten oder starten, wird dieser Vorgang im entsprechenden Callback gestoppt oder entfernt. Wenn du ihn beendest, wird er wieder ausgeführt, wenn er nicht mehr benötigt wird. Wenn du beispielsweise einen Timer in onStart() einrichten möchtest, musst du den Timer in onStop() pausieren oder stoppen.
  • Verwende onCreate() nur zum Initialisieren der Teile deiner App, die beim ersten Mal ausgeführt werden. Du kannst onStart() verwenden, um die Teile der App zu starten, die sowohl gestartet als auch immer dann gestartet werden, wenn die App in den Vordergrund zurückkehrt.

Lebenszyklus-Bibliothek

  • Mit der Android-Lebenszyklus-Bibliothek können Sie die Lebenszyklussteuerung von der Aktivität oder dem Fragment auf die eigentliche Komponente verschieben, die den Lebenszyklus berücksichtigen muss.
  • Inhaber des Lebenszyklus sind Komponenten mit einem und somit einem eigenen Lebenszyklus, einschließlich Activity und Fragment. Lebenszyklusinhaber implementieren die LifecycleOwner-Schnittstelle.
  • Observatoren überwachen den aktuellen Lebenszyklus und führen Aufgaben aus, wenn sich der Lebenszyklus ändert. Lebenszyklusbetrachter verwenden die Schnittstelle LifecycleObserver.
  • Lifecycle-Objekte enthalten die tatsächlichen Lebenszyklusstatus und lösen Ereignisse aus, wenn sich der Lebenszyklus ändert.

So erstellen Sie eine Lebenszyklussensitive Klasse:

  • Implementiere die LifecycleObserver-Schnittstelle in Klassen, die den Lebenszyklus kennen.
  • Initialisieren Sie eine Lebenszyklus- Observer-Klasse mit dem Lebenszyklusobjekt aus der Aktivität oder dem Fragment.
  • In der Lebenszyklusbeobachterklasse können Sie sitzungsspezifische Methoden mit der Änderung ihres Lebenszyklusstatus annotieren.

    Die Annotation @OnLifecycleEvent(Lifecycle.Event.ON_START) gibt beispielsweise an, dass die Methode das Lebenszyklusereignis onStart überwacht.

Herunterfahren und Speichern des Aktivitätsstatus

  • Android reguliert im Hintergrund ausgeführte Apps so, dass die App im Vordergrund ohne Probleme ausgeführt werden kann. Dazu gehört auch, den Umfang der Verarbeitung von Apps im Hintergrund zu beschränken und manchmal sogar den gesamten App-Prozess abzubrechen.
  • Der Nutzer kann nicht erkennen, ob das System eine App im Hintergrund beendet hat. Die App wird weiterhin im Bildschirm „Zuletzt verwendet“ 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 angehängt sind. Mit adb können Sie das Herunterfahren von Prozessen in Ihrer App simulieren.
  • Wenn Android deinen App-Prozess herunterfährt, wird die Lebenszyklusmethode onDestroy() nicht aufgerufen. Die App endet einfach.

Aktivitäten und Fragmentstatus beibehalten

  • Wenn deine App in den Hintergrund geht, kurz nach dem Aufruf von onStop(), werden App-Daten in einem Bundle gespeichert. Einige App-Daten, wie die Inhalte eines EditText, werden automatisch für dich gespeichert.
  • Das Bundle ist eine Instanz von Bundle, einer Sammlung von Schlüsseln und Werten. Die Schlüssel sind immer Strings.
  • Mit dem Callback onSaveInstanceState() können Sie andere Daten in dem Bundle speichern, das Sie beibehalten möchten, auch wenn die App automatisch heruntergefahren wurde. Wenn Sie Daten in das Bundle aufnehmen möchten, verwenden Sie die Set-Methoden, die mit put beginnen, z. B. putInt().
  • Du kannst Daten mit der Methode onRestoreInstanceState() oder besser in onCreate() wieder aus dem Bundle abrufen. Die Methode onCreate() hat einen Parameter savedInstanceState, der das Bundle enthält.
  • Wenn die Variable savedInstanceState null enthält, wurde die Aktivität ohne 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 mit get beginnen, z. B. getInt().

Änderungen der Konfiguration

  • Eine Konfigurationsänderung findet statt, wenn der Status des Geräts radikal geändert wird. Dadurch kann das System die Änderung am einfachsten beheben, indem die Aktivität beendet und neu erstellt wird.
  • Das häufigste Beispiel für eine Konfigurationsänderung ist, wenn der Nutzer das Gerät vom Hoch- ins Querformat oder vom Hoch- ins Querformat wechselt. Eine Konfigurationsänderung kann auch auftreten, wenn sich die Gerätesprache ändert oder eine Hardwaretastatur angeschlossen ist.
  • Wenn eine Konfigurationsänderung stattfindet, ruft Android den gesamten Lebenszyklus eines Herunterfahrens der Aktivität auf. Anschließend startet Android die Aktivität von Grund auf neu und führt alle Lebenszyklus-Start-Callbacks aus.
  • Wenn Android eine App aufgrund einer Konfigurationsänderung herunterfährt, wird die Aktivität mit dem Status-Bundle neu gestartet, das für onCreate() verfügbar ist.
  • Wie beim Herunterfahren des Vorgangs müssen Sie den Status Ihrer App im Bundle in onSaveInstanceState() speichern.

Udacity-Kurs:

Android-Entwicklerdokumentation:

Sonstiges:

In diesem Abschnitt werden mögliche Hausaufgaben für Schüler oder Studenten aufgeführt, die an diesem von einem Kursleiter geleiteten Codelab arbeiten. Die Lehrkraft kann Folgendes tun:

  • Bei Bedarf können Sie die entsprechenden Aufgaben zuweisen.
  • Schülern mitteilen, wie sie Aufgaben für die Aufgabe abgeben
  • Benoten Sie die Hausaufgaben.

Lehrkräfte können diese Vorschläge so oft oder so oft verwenden, wie sie möchten. anderen Aufgaben können sie nach Belieben zugewiesen werden.

Wenn Sie alleine an diesem Codelab arbeiten, können Sie Ihr Wissen mit diesen Hausaufgaben testen.

Eine App ändern

Öffnen Sie die DiceRoller App aus Lektion 1. Hier können Sie die App herunterladen. Kompiliere und führe die App aus. Wenn du das Gerät drehst, geht der aktuelle Wert des Würfels verloren. Implementiere onSaveInstanceState(), um diesen Wert im Bundle beizubehalten und in onCreate() wiederherzustellen.

Diese Fragen beantworten

Frage 1

Deine App enthält eine physikalische Simulation, die eine hohe Rechenleistung erfordert. Dann erhält der Nutzer einen Telefonanruf. Welche der folgenden Aussagen ist richtig?

  • Während des Anrufs sollten Sie mit der Berechnung der Positionen von Objekten in der physikalischen Simulation fortfahren.
  • Während des Anrufs solltest du die Position von Objekten in der physikalischen Simulation nicht mehr berechnen.

Frage 2

Welche Lebenszyklusmethode solltest du überschreiben, um die Simulation anzuhalten, wenn die App nicht auf dem Bildschirm angezeigt wird?

  • onDestroy()
  • onStop()
  • onPause()
  • onSaveInstanceState()

Frage 3

Welche Schnittstellen sollten implementiert werden, um den Lebenszyklus einer Klasse über die Android-Lebenszyklus-Bibliothek zu steuern?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

Frage 4

Unter welchen Umständen erhält die onCreate()-Methode in Ihrer Aktivität eine Bundle mit Daten (d. h. Bundle ist nicht null)? Möglicherweise gibt es mehrere Antworten.

  • Die Aktivität wird nach dem Drehen des Geräts neu gestartet.
  • Die Aktivität beginnt von vorn.
  • Die Aktivität wird fortgesetzt, nachdem sie im Hintergrund ausgeführt wurde.
  • Das Gerät wird neu gestartet.

Starten Sie die nächste Lektion: 5.1: ViewModel und ViewModelFactory

Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage zu Kotlin-Grundlagen für Android.