Umgang mit Lebenszyklen mit lebenszyklusbewussten Komponenten Teil von Android Jetpack.
Lebenszyklusorientierte 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 leichteren Code erstellen, der einfacher zu verwalten ist.
Ein gängiges Muster ist die Implementierung der Aktionen der abhängigen Komponenten in den Lebenszyklusmethoden von Aktivitäten und Fragmenten. Dieses Muster führt jedoch zu einer schlechten Organisation des Codes und zu einer Vermehrung von Fehlern. Wenn Sie sitzungsspezifische Komponenten verwenden, können Sie den Code abhängiger Komponenten aus den Lebenszyklusmethoden in die Komponenten selbst verschieben.
Das Paket androidx.lifecycle
bietet Klassen und Schnittstellen, mit denen Sie lebenszyklusorientierte Komponenten erstellen können. Das sind Komponenten, die ihr Verhalten automatisch an den aktuellen Lebenszyklusstatus einer Aktivität oder eines Fragments anpassen können.
Die meisten App-Komponenten, die im Android-Framework definiert sind, haben Lebenszyklen. Lebenszyklen werden vom Betriebssystem oder vom Framework-Code verwaltet, der in Ihrem Prozess ausgeführt wird. Sie sind für die Funktionsweise von Android von entscheidender Bedeutung und Ihre App muss sie einhalten. Andernfalls kann es zu Speicherlecks oder sogar zu Anwendungsabstürzen kommen.
Angenommen, wir haben 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 in Ordnung aussieht, gibt es in einer echten App zu viele Aufrufe, die die Benutzeroberfläche und andere Komponenten als Reaktion auf den aktuellen Zustand des Lebenszyklus verwalten. Wenn Sie mehrere Komponenten verwalten, wird in Lebenszyklusmethoden wie onStart()
und onStop()
ein erheblicher Code eingefügt, was die Wartung erschwert.
Außerdem gibt es keine Garantie dafür, dass die Komponente gestartet wird, bevor die Aktivität oder das Fragment beendet wird. Das gilt insbesondere, wenn wir einen langwierigen Vorgang ausführen müssen, z. B. eine Konfigurationsüberprüfung in onStart()
. Dies kann zu einer Race-Bedingung führen, bei der die onStop()
-Methode vor der onStart()
beendet wird, wodurch die Komponente länger als nötig aktiv bleibt.
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 resiliente und isolierte Weise angehen können.
Lebenszyklus
Lifecycle
ist eine Klasse, 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 Hauptaufzählungen, um den Lebenszyklusstatus der zugehörigen Komponente zu verfolgen:
- Veranstaltung
- Die Lebenszyklusereignisse, die vom Framework und der Klasse
Lifecycle
gesendet werden. Diese Ereignisse werden den Rückrufereignissen in Aktivitäten und Fragmenten zugeordnet. - Bundesland
- Der aktuelle Status der Komponente, die vom
Lifecycle
-Objekt erfasst wird.
Stellen Sie sich die Zustände als Knoten eines Graphen und die Ereignisse als Kanten zwischen diesen Knoten vor.
Eine Klasse kann den Lebenszyklusstatus der Komponente überwachen, indem sie DefaultLifecycleObserver
implementiert und entsprechende Methoden wie onCreate
und onStart
überschreibt. Dann können Sie einen Beobachter hinzufügen, indem Sie die Methode addObserver()
der Klasse Lifecycle
aufrufen und eine Instanz Ihres 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 obigen Beispiel implementiert das myLifecycleOwner
-Objekt die Schnittstelle LifecycleOwner
, die im nächsten Abschnitt erläutert wird.
LifecycleOwner
LifecycleOwner
ist eine Schnittstelle mit einer einzelnen Methode, die angibt, dass die Klasse eine Lifecycle
hat. Sie hat eine Methode, getLifecycle()
, die von der Klasse implementiert werden muss.
Wenn Sie stattdessen den Lebenszyklus eines gesamten Anwendungsvorgangs verwalten möchten, lesen Sie den Hilfeartikel ProcessLifecycleOwner
.
Diese Schnittstelle abstrahiert die Inhaberschaft einer Lifecycle
von einzelnen Klassen wie Fragment
und AppCompatActivity
und ermöglicht das Schreiben von Komponenten, die damit funktionieren. Jede benutzerdefinierte Anwendungsklasse kann die Schnittstelle LifecycleOwner
implementieren.
Komponenten, die DefaultLifecycleObserver
implementieren, funktionieren nahtlos mit Komponenten, die LifecycleOwner
implementieren, da ein Inhaber einen Lebenszyklus angeben kann, den ein Beobachter registrieren und beobachten kann.
Für das Beispiel zum Standort-Tracking können wir die Klasse MyLocationListener
DefaultLifecycleObserver
implementieren und sie dann in der Methode onCreate()
mit dem Lifecycle
der Aktivität initialisieren. So ist die MyLocationListener
-Klasse autark. Das bedeutet, dass die Logik, die auf Änderungen des Lebenszyklusstatus reagiert, in MyLocationListener
statt in der Aktivität deklariert wird. Wenn die einzelnen Komponenten ihre eigene Logik speichern, lässt sich die Logik der Aktivitäten und Fragmente leichter 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 ist das Vermeiden des Aufrufs bestimmter Rückrufe, wenn sich Lifecycle
nicht in einem guten Zustand befindet. 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 zu vereinfachen, 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 } }
Mit dieser Implementierung ist unsere LocationListener
-Klasse vollständig sitzungsorientiert. Wenn wir unsere LocationListener
aus einer anderen Aktivität oder einem anderen Fragment verwenden möchten, müssen wir sie nur initialisieren. Alle Einrichtungs- und Deaktivierungsvorgänge werden von der Klasse selbst verwaltet.
Wenn eine Bibliothek Klassen bereitstellt, die mit dem Android-Lebenszyklus funktionieren müssen, empfehlen wir, lebenszyklusbewusste Komponenten zu verwenden. Ihre Bibliothekskunden können diese Komponenten ganz einfach einbinden, ohne dass auf der Clientseite eine manuelle Lebenszyklusverwaltung erforderlich ist.
Benutzerdefinierten LifecycleOwner implementieren
Fragmente und Aktivitäten in der Support Library 26.1.0 und höher implementieren bereits die LifecycleOwner
-Benutzeroberfläche.
Wenn Sie eine benutzerdefinierte Klasse haben, aus der Sie einen 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 sitzungsspezifische Komponenten
- Halten Sie Ihre UI-Controller (Aktivitäten und Fragmente) so schlank wie möglich. Sie sollten nicht versuchen, eigene Daten zu erheben, sondern stattdessen ein
ViewModel
verwenden und einLiveData
-Objekt beobachten, um die Änderungen in den Ansichten widerzuspiegeln. - Versuchen Sie, datengestützte UIs zu erstellen, bei denen der UI-Controller dafür verantwortlich ist, die Ansichten bei Datenänderungen zu aktualisieren oder Nutzeraktionen an die
ViewModel
zurückzugeben. - Platzieren Sie die Datenlogik in der Klasse
ViewModel
.ViewModel
sollte als Verbindung zwischen Ihrem UI-Controller und dem Rest Ihrer App dienen. Es ist jedoch nicht die Aufgabe vonViewModel
, Daten abzurufen (z. B. aus einem Netzwerk). Stattdessen sollteViewModel
die entsprechende Komponente aufrufen, um die Daten abzurufen, und das Ergebnis dann an den UI-Controller zurückgeben. - Verwenden Sie Datenbindungen, um eine klare Benutzeroberfläche zwischen Ihren Ansichten und dem UI-Controller zu erhalten. So können Sie Ihre Ansichten deklarativer gestalten und den Aktualisierungscode minimieren, den Sie in Ihren Aktivitäten und Fragmenten schreiben müssen. Wenn Sie dies lieber in der Programmiersprache Java tun möchten, verwenden Sie eine Bibliothek wie Butter Knife, um Boilerplate-Code zu vermeiden und eine bessere Abstraktion zu erhalten.
- Wenn Ihre Benutzeroberfläche komplex ist, können Sie eine Presenter-Klasse erstellen, um Änderungen an der Benutzeroberfläche zu verarbeiten. Das kann zwar mühsam sein, aber es erleichtert das Testen Ihrer UI-Komponenten.
- Vermeiden Sie in
ViewModel
Verweise auf einenView
- oderActivity
-Kontext. Wenn dieViewModel
die Aktivität überlebt (bei Konfigurationsänderungen), kommt es zu einem Leck und die Aktivität wird vom Garbage Collector nicht ordnungsgemäß beseitigt. - Verwenden Sie Kotlin-Coroutinen, um lang andauernde Aufgaben und andere Vorgänge zu verwalten, die asynchron ausgeführt werden können.
Anwendungsfälle für sitzungsspezifische Komponenten
Mit lebenszyklusorientierten Komponenten können Sie in vielen Fällen den Lebenszyklus viel einfacher verwalten. Beispiele:
- Zwischen groben und detaillierten Standortaktualisierungen wechseln Verwenden Sie standortbezogene Komponenten, um detaillierte Standortaktualisierungen zu aktivieren, während Ihre Standort-App sichtbar ist, und zu grobkörnigen Aktualisierungen zu wechseln, wenn die App im Hintergrund ausgeführt wird. Mit
LiveData
, einer sitzungsorientierten Komponente, kann die Benutzeroberfläche Ihrer App automatisch aktualisiert werden, wenn sich der Nutzer an einem anderen Ort befindet. - Videopufferung anhalten und starten Verwenden Sie schleifenspezifische Komponenten, um das Video-Buffering so schnell wie möglich zu starten, aber die Wiedergabe bis zum vollständigen Starten der App zu verschieben. Sie können auch sitzungsspezifische Komponenten verwenden, um die Pufferung zu beenden, wenn Ihre App zerstört wird.
- Netzwerkverbindung starten und beenden Mit sitzungsorientierten Komponenten können Sie die Liveaktualisierung (das Streaming) von Netzwerkdaten aktivieren, während sich eine App im Vordergrund befindet, und sie automatisch pausieren, wenn die App in den Hintergrund wechselt.
- Animierte Drawables pausieren und fortsetzen Verwenden Sie sitzungsabhängige Komponenten, um animierte drawables anzuhalten, wenn sich die App im Hintergrund befindet, und sie fortzusetzen, wenn sich die App im Vordergrund befindet.
Verarbeitung von Stopp-Ereignissen
Wenn ein Lifecycle
zu einem AppCompatActivity
oder Fragment
gehört, ändert sich der Status von Lifecycle
zu CREATED
und das Ereignis ON_STOP
wird gesendet, wenn die onSaveInstanceState()
von AppCompatActivity
oder Fragment
aufgerufen wird.
Wenn der Status einer Fragment
oder AppCompatActivity
über onSaveInstanceState()
gespeichert wird, gilt die Benutzeroberfläche als unveränderlich, bis ON_START
aufgerufen wird. Wenn Sie versuchen, die Benutzeroberfläche nach dem Speichern des Status zu ändern, führt dies wahrscheinlich zu Inkonsistenzen im Navigationsstatus Ihrer Anwendung. Daher wirft FragmentManager
eine Ausnahme aus, wenn die App nach dem Speichern des Status eine FragmentTransaction
ausführt. Weitere Informationen finden Sie unter commit()
.
LiveData
verhindert diesen Grenzfall standardmäßig, indem der Beobachter nicht aufgerufen wird, wenn der zugehörige Lifecycle
nicht mindestens STARTED
beträgt.
Im Hintergrund wird isAtLeast()
aufgerufen, bevor entschieden wird, ob der Beobachter aufgerufen werden soll.
Leider wird die onStop()
-Methode von AppCompatActivity
nach
onSaveInstanceState()
aufgerufen. Dadurch entsteht eine Lücke, in der Änderungen des UI-Status nicht zulässig sind, Lifecycle
aber noch nicht in den Status CREATED
gewechselt wurde.
Um dieses Problem zu vermeiden, wird in der Klasse Lifecycle
in Version beta2
und niedriger der Status als CREATED
gekennzeichnet, ohne das Ereignis zu senden. So erhält jeder Code, der den aktuellen Status prüft, den tatsächlichen Wert, auch wenn das Ereignis erst gesendet wird, wenn onStop()
vom System aufgerufen wird.
Leider hat diese Lösung zwei große Probleme:
- Bei API-Level 23 und niedriger speichert das Android-System den Status einer Aktivität auch dann, wenn sie 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 weiterhin davon ausgeht, dass der Lebenszyklus aktiv ist, obwohl sein UI-Status nicht geändert werden kann. - Jede Klasse, die ein ähnliches Verhalten wie die Klasse
LiveData
bieten soll, muss die Problemumgehung implementieren, die inLifecycle
Versionbeta 2
und niedriger verfügbar ist.
Weitere Informationen
Weitere Informationen zum Umgang mit Lebenszyklen mit lebenszyklusbewussten Komponenten finden Sie in den folgenden zusätzlichen Ressourcen.
Produktproben
- Sonnenblume, eine Demo-App, die Best Practices mit Architekturkomponenten zeigt
Codelabs
Blogs
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- LiveData – Übersicht
- Kotlin-Coroutinen mit sitzungsabhängigen Komponenten verwenden
- Modul für den gespeicherten Status für ViewModel