Umgang mit Lebenszyklen mit lebenszyklusbewussten Komponenten Teil von Android Jetpack
Lebenszyklussensitive Komponenten führen Aktionen als Reaktion auf eine Änderung des Lebenszyklusstatus einer anderen Komponente aus, z. B. Aktivitäten und Fragmente. Mit diesen Komponenten können Sie besser organisierten und oft schlanken Code erstellen, der einfacher zu verwalten ist.
Ein gemeinsames Muster besteht darin, die Aktionen der abhängigen Komponenten in die Lebenszyklusmethoden der Aktivitäten und Fragmente zu implementieren. Dieses Muster führt jedoch zu einer schlechten Organisation des Codes und einer Zunahme von Fehlern. Mit Komponenten, die den Lebenszyklus berücksichtigen, können Sie den Code der abhängigen Komponenten aus den Lebenszyklusmethoden in die Komponenten selbst verschieben.
Das Paket androidx.lifecycle
bietet Klassen und Schnittstellen, mit denen Sie Lebenszyklus berücksichtigen Komponenten erstellen können. Das sind Komponenten, die ihr Verhalten automatisch an den aktuellen Lebenszyklusstatus einer Aktivität oder eines Fragments anpassen können.
Den meisten im Android Framework definierten App-Komponenten sind Lebenszyklen zugeordnet. Lebenszyklen werden vom Betriebssystem oder dem in Ihrem Prozess ausgeführten Framework-Code verwaltet. Sie sind die Grundlage für die Funktionsweise von Android und deine App muss sie berücksichtigen. Andernfalls kann es zu Speicherlecks oder sogar Anwendungsabstürzen kommen.
Stellen Sie sich vor, es gibt eine Aktivität, bei der der Gerätestandort auf dem Bildschirm angezeigt wird. Eine gängige Implementierung könnte so aussehen:
Kotlin
internal class MyLocationListener( private val context: Context, private val callback: (Location) -> Unit ) { fun start() { // connect to system location service } fun stop() { // disconnect from system location service } } class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() myLocationListener.start() // manage other components that need to respond // to the activity lifecycle } public override fun onStop() { super.onStop() myLocationListener.stop() // manage other components that need to respond // to the activity lifecycle } }
Java
class MyLocationListener { public MyLocationListener(Context context, Callback callback) { // ... } void start() { // connect to system location service } void stop() { // disconnect from system location service } } class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; @Override public void onCreate(...) { myLocationListener = new MyLocationListener(this, (location) -> { // update UI }); } @Override public void onStart() { super.onStart(); myLocationListener.start(); // manage other components that need to respond // to the activity lifecycle } @Override public void onStop() { super.onStop(); myLocationListener.stop(); // manage other components that need to respond // to the activity lifecycle } }
Auch wenn dieses Beispiel gut aussieht, haben Sie in einer echten Anwendung am Ende zu viele Aufrufe, mit denen die UI und andere Komponenten als Reaktion auf den aktuellen Zustand des Lebenszyklus verwaltet werden. Bei der Verwaltung mehrerer Komponenten ist eine beträchtliche Menge an Code in Lebenszyklusmethoden wie onStart()
und onStop()
enthalten, was ihre Verwaltung erschwert.
Außerdem kann nicht garantiert werden, dass die Komponente gestartet wird, bevor die Aktivität oder das Fragment gestoppt wird. Dies gilt insbesondere, wenn wir einen Vorgang mit langer Ausführungszeit ausführen müssen, z. B. eine Konfigurationsprüfung in onStart()
. Dies kann zu einer Race-Bedingung führen, bei der die Methode onStop()
vor dem onStart()
beendet wird, sodass die Komponente länger aktiv bleibt als nötig.
Kotlin
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() Util.checkUserStatus { result -> // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start() } } } public override fun onStop() { super.onStop() myLocationListener.stop() } }
Java
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, location -> { // update UI }); } @Override public void onStart() { super.onStart(); Util.checkUserStatus(result -> { // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start(); } }); } @Override public void onStop() { super.onStop(); myLocationListener.stop(); } }
Das Paket androidx.lifecycle
bietet Klassen und Schnittstellen, mit denen Sie diese Probleme auf stabile und isolierte Weise angehen können.
Lebenszyklus
Lifecycle
ist eine Klasse, die die Informationen zum Lebenszyklusstatus einer Komponente (z. B. einer Aktivität oder eines Fragments) enthält und es anderen Objekten ermöglicht, diesen Status zu beobachten.
Lifecycle
verwendet zwei Hauptauflistungen, um den Lebenszyklusstatus für die zugehörige Komponente zu verfolgen:
- Veranstaltung
- Die Lebenszyklusereignisse, die vom Framework und der Klasse
Lifecycle
gesendet werden. Diese Ereignisse werden den Callback-Ereignissen in Aktivitäten und Fragmenten zugeordnet. - Bundesland
- Der aktuelle Status der Komponente, die vom Objekt
Lifecycle
erfasst wird.
Stellen Sie sich die Status als Knoten einer Grafik und Ereignisse als die Kanten zwischen diesen Knoten vor.
Eine Klasse kann den Lebenszyklusstatus der Komponente überwachen, indem sie DefaultLifecycleObserver
implementiert und entsprechende Methoden wie onCreate
, onStart
usw. überschreibt. Anschließend können Sie einen Beobachter hinzufügen, indem Sie die Methode addObserver()
der Klasse Lifecycle
aufrufen und eine Instanz des Beobachters übergeben, wie im folgenden Beispiel gezeigt:
Kotlin
class MyObserver : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { connect() } override fun onPause(owner: LifecycleOwner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(MyObserver())
Java
public class MyObserver implements DefaultLifecycleObserver { @Override public void onResume(LifecycleOwner owner) { connect() } @Override public void onPause(LifecycleOwner owner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
Im Beispiel oben implementiert das myLifecycleOwner
-Objekt die Schnittstelle LifecycleOwner
, die im folgenden Abschnitt erläutert wird.
Lebenszyklusinhaber
LifecycleOwner
ist eine einzelne Methodenschnittstelle, die angibt, dass die Klasse eine Lifecycle
hat. Es gibt eine Methode, getLifecycle()
, die von der Klasse implementiert werden muss.
Wenn Sie stattdessen den Lebenszyklus eines gesamten Anwendungsprozesses verwalten möchten, finden Sie weitere Informationen unter ProcessLifecycleOwner
.
Diese Schnittstelle abstrahiert die Inhaberschaft einer Lifecycle
aus einzelnen Klassen wie Fragment
und AppCompatActivity
und ermöglicht das Schreiben von Komponenten, die mit diesen Klassen funktionieren. Die LifecycleOwner
-Schnittstelle kann von jeder benutzerdefinierten Anwendungsklasse implementiert werden.
Komponenten, in denen DefaultLifecycleObserver
implementiert wird, arbeiten nahtlos mit Komponenten zusammen, die LifecycleOwner
implementieren, da ein Inhaber einen Lebenszyklus angeben kann, den ein Beobachter zum Ansehen registrieren kann.
Für das Standort-Tracking-Beispiel können wir die MyLocationListener
-Klasse dazu bringen, DefaultLifecycleObserver
zu implementieren, und sie dann mit dem Lifecycle
der Aktivität in der onCreate()
-Methode initialisieren. Dadurch kann die Klasse MyLocationListener
unabhängig sein. Das bedeutet, dass die Logik, um auf Änderungen des Lebenszyklusstatus zu reagieren, in MyLocationListener
und nicht in der Aktivität deklariert wird. Wenn die einzelnen Komponenten ihre eigene Logik speichern, ist die Logik für Aktivitäten und Fragmente einfacher zu verwalten.
Kotlin
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this, lifecycle) { location -> // update UI } Util.checkUserStatus { result -> if (result) { myLocationListener.enable() } } } }
Java
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, getLifecycle(), location -> { // update UI }); Util.checkUserStatus(result -> { if (result) { myLocationListener.enable(); } }); } }
Ein häufiger Anwendungsfall besteht darin, den Aufruf bestimmter Callbacks zu vermeiden, wenn Lifecycle
momentan nicht in einem guten Zustand ist. Wenn der Callback beispielsweise eine Fragmenttransaktion ausführt, nachdem der Aktivitätsstatus gespeichert wurde, würde dies einen Absturz auslösen. Daher sollte dieser Callback nie aufgerufen werden.
Um diesen Anwendungsfall einfach zu gestalten, ermöglicht die Klasse Lifecycle
anderen Objekten, den aktuellen Status abzufragen.
Kotlin
internal class MyLocationListener( private val context: Context, private val lifecycle: Lifecycle, private val callback: (Location) -> Unit ): DefaultLifecycleObserver { private var enabled = false override fun onStart(owner: LifecycleOwner) { if (enabled) { // connect } } fun enable() { enabled = true if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // connect if not connected } } override fun onStop(owner: LifecycleOwner) { // disconnect if connected } }
Java
class MyLocationListener implements DefaultLifecycleObserver { private boolean enabled = false; public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { ... } @Override public void onStart(LifecycleOwner owner) { if (enabled) { // connect } } public void enable() { enabled = true; if (lifecycle.getCurrentState().isAtLeast(STARTED)) { // connect if not connected } } @Override public void onStop(LifecycleOwner owner) { // disconnect if connected } }
Bei dieser Implementierung berücksichtigt die LocationListener
-Klasse den Lebenszyklus vollständig. Wenn wir unser LocationListener
aus einer anderen Aktivität oder einem anderen Fragment verwenden müssen, müssen wir es nur initialisieren. Alle Einrichtungs- und Teardown-Vorgänge werden von der Klasse selbst verwaltet.
Wenn eine Bibliothek Klassen enthält, die mit dem Android-Lebenszyklus funktionieren müssen, empfehlen wir die Verwendung von Komponenten, die den Lebenszyklus berücksichtigen. Ihre Bibliotheksclients können diese Komponenten problemlos einbinden, ohne den Lebenszyklus manuell verwalten zu müssen.
Benutzerdefinierten LifecycleOwner implementieren
Für Fragmente und Aktivitäten in Support Library 26.1.0 und höher wird bereits die Schnittstelle LifecycleOwner
implementiert.
Wenn Sie eine benutzerdefinierte Klasse haben und eine LifecycleOwner
erstellen möchten, können Sie die Klasse LifecycleRegistry verwenden. Sie müssen jedoch Ereignisse an diese Klasse weiterleiten, wie im folgenden Codebeispiel gezeigt:
Kotlin
class MyActivity : Activity(), LifecycleOwner { private lateinit var lifecycleRegistry: LifecycleRegistry override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleRegistry = LifecycleRegistry(this) lifecycleRegistry.markState(Lifecycle.State.CREATED) } public override fun onStart() { super.onStart() lifecycleRegistry.markState(Lifecycle.State.STARTED) } override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class MyActivity extends Activity implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } @Override public void onStart() { super.onStart(); lifecycleRegistry.markState(Lifecycle.State.STARTED); } @NonNull @Override public Lifecycle getLifecycle() { return lifecycleRegistry; } }
Best Practices für Komponenten, bei denen der Lebenszyklus berücksichtigt wird
- Halten Sie Ihre UI-Controller (Aktivitäten und Fragmente) so schlank wie möglich. Sie sollten nicht versuchen, ihre eigenen Daten abzurufen. Verwenden Sie stattdessen ein
ViewModel
dafür und beobachten Sie einLiveData
-Objekt, um die Änderungen in den Ansichten widerzuspiegeln. - Versuchen Sie, datengesteuerte UIs zu schreiben, bei denen Ihr UI-Controller dafür verantwortlich ist, die Ansichten bei Datenänderungen zu aktualisieren, oder informieren Sie Nutzeraktionen an die
ViewModel
. - Fügen Sie die Datenlogik der
ViewModel
-Klasse hinzu.ViewModel
sollte als Connector zwischen dem UI-Controller und dem Rest der App dienen. Aber Vorsicht: Es ist nicht die Aufgabe vonViewModel
, Daten abzurufen (z. B. aus einem Netzwerk). Stattdessen sollteViewModel
die entsprechende Komponente zum Abrufen der Daten aufrufen und das Ergebnis dann an den UI-Controller zurückgeben. - Verwenden Sie Datenbindung, um eine übersichtliche Schnittstelle zwischen Ihren Ansichten und dem UI-Controller zu erhalten. Auf diese Weise können Sie Ihre Ansichten deklarativer gestalten und den Aktualisierungscode minimieren, den Sie in Ihre Aktivitäten und Fragmente schreiben müssen. Wenn Sie dies in der Programmiersprache Java vorziehen, sollten Sie eine Bibliothek wie Butter Knife verwenden, um Boilerplate-Code zu vermeiden und eine bessere Abstraktion zu ermöglichen.
- Wenn Ihre UI komplex ist, sollten Sie gegebenenfalls eine Presenter-Klasse für Änderungen der UI erstellen. Das ist vielleicht eine arbeitsintensive Aufgabe, kann aber das Testen Ihrer UI-Komponenten vereinfachen.
- Verweisen Sie in
ViewModel
nicht auf einenView
- oderActivity
-Kontext. WennViewModel
die Aktivität überschreitet (bei Konfigurationsänderungen), treten Datenlecks auf und werden von der automatischen Speicherbereinigung nicht ordnungsgemäß entsorgt. - Mit Kotlin-Koroutinen können Sie lang andauernde Aufgaben und andere Vorgänge verwalten, die asynchron ausgeführt werden können.
Anwendungsfälle für Komponenten, bei denen der Lebenszyklus berücksichtigt wird
Lebenszyklussensitive Komponenten können die Verwaltung von Lebenszyklen in verschiedenen Fällen erheblich vereinfachen. Hier einige Beispiele:
- Zwischen groben und detaillierten Standortupdates wechseln Mit Komponenten, die den Lebenszyklus berücksichtigen, können Sie präzise Standortupdates aktivieren, während die Standort-App sichtbar ist. Außerdem können Sie zu groben Aktualisierungen wechseln, wenn die App im Hintergrund ausgeführt wird. Mit
LiveData
, einer Komponente, die den Lebenszyklus berücksichtigt, kann Ihre App die UI automatisch aktualisieren, wenn Nutzer den Standort wechseln. - Die Videozwischenspeicherung wird beendet und gestartet. Verwenden Sie Komponenten, bei denen der Lebenszyklus berücksichtigt wird, um die Zwischenspeicherung von Videos so schnell wie möglich zu starten und die Wiedergabe so lange zu verschieben, bis die App vollständig gestartet wurde. Sie können Komponenten, die den Lebenszyklus berücksichtigen, auch verwenden, um die Zwischenspeicherung beim Löschen Ihrer Anwendung zu beenden.
- Netzwerkverbindung wird gestartet und beendet. Verwenden Sie Komponenten, bei denen der Lebenszyklus berücksichtigt wird, um Live-Updates (Streaming) von Netzwerkdaten zu ermöglichen, während eine App im Vordergrund ausgeführt wird, und um automatisch zu pausieren, wenn die App in den Hintergrund versetzt wird.
- Animierte Drawables pausieren und fortsetzen Verwende Komponenten, die den Lebenszyklus berücksichtigen, das Pausieren animierter Drawables, wenn die App im Hintergrund läuft, und die Drawables fortzusetzen, nachdem die App im Vordergrund ausgeführt wurde.
Umgang mit Stoppereignissen
Wenn ein Lifecycle
zu AppCompatActivity
oder Fragment
gehört, ändert sich der Status des Lifecycle
in CREATED
und das Ereignis ON_STOP
wird ausgelöst, wenn AppCompatActivity
oder Fragment
onSaveInstanceState()
aufgerufen wird.
Wenn der Status eines Fragment
- oder AppCompatActivity
-Objekts über onSaveInstanceState()
gespeichert wird, gilt seine UI als unveränderlich, bis ON_START
aufgerufen wird. Wenn Sie versuchen, die UI zu ändern, nachdem der Status gespeichert wurde, kann dies zu Inkonsistenzen im Navigationsstatus Ihrer Anwendung führen. Daher gibt FragmentManager
eine Ausnahme aus, wenn die App nach dem Speichern des Status ein FragmentTransaction
ausführt. Weitere Informationen finden Sie unter commit()
.
LiveData
verhindert standardmäßig diesen Grenzfall. Der Beobachter wird nicht aufgerufen, wenn der mit dem Beobachter verknüpfte Lifecycle
nicht mindestens STARTED
ist.
Im Hintergrund wird isAtLeast()
aufgerufen, bevor entschieden wird, der Beobachter aufzurufen.
Leider wird die Methode onStop()
von AppCompatActivity
nach
onSaveInstanceState()
aufgerufen. Dadurch entsteht eine Lücke, bei der Änderungen des UI-Status nicht zulässig sind, die Lifecycle
jedoch noch nicht in den Status CREATED
verschoben wurde.
Um dieses Problem zu vermeiden, markieren die Klasse Lifecycle
in Version beta2
und niedriger den Status als CREATED
, ohne das Ereignis auszulösen, sodass jeder Code, der den aktuellen Status prüft, den tatsächlichen Wert erhält, obwohl das Ereignis erst ausgelöst wird, wenn onStop()
vom System aufgerufen wird.
Leider weist diese Lösung zwei große Probleme auf:
- Auf API-Level 23 und niedriger speichert das Android-System den Status einer Aktivität tatsächlich, auch wenn er teilweise von einer anderen Aktivität abgedeckt ist. Mit anderen Worten: Das Android-System ruft
onSaveInstanceState()
auf, aber nicht unbedingtonStop()
. Dadurch entsteht ein potenziell langes Intervall, in dem der Beobachter immer noch davon ausgeht, dass der Lebenszyklus aktiv ist, obwohl der Status der Benutzeroberfläche nicht geändert werden kann. - Jede Klasse, die ein ähnliches Verhalten wie die Klasse
LiveData
zeigen möchte, muss die Problemumgehung vonLifecycle
Versionbeta 2
und niedriger implementieren.
Weitere Informationen
Weitere Informationen zum Umgang mit Lebenszyklen mit Komponenten, die den Lebenszyklus berücksichtigen, finden Sie in den folgenden zusätzlichen Ressourcen.
Produktproben
- Basisbeispiel für Android-Architekturkomponenten
- Sunflower, eine Demo-App, die Best Practices für Architekturkomponenten zeigt
Codelabs
Blogs
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- LiveData – Übersicht
- Kotlin-Koroutinen mit Komponenten verwenden, die den Lebenszyklus berücksichtigen
- Modul „Gespeicherter Status“ für ViewModel