Konzepte und Jetpack Compose-Implementierung
Lebenszyklusbezogene 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 warten ist.
Ein häufiges Muster besteht darin, die Aktionen der abhängigen Komponenten in den Lebenszyklusmethoden von Aktivitäten und Fragmenten zu implementieren. Dieses Muster führt jedoch zu einer schlechten Organisation des Codes und zu einer Zunahme von Fehlern. Mit lebenszyklusbezogenen Komponenten können Sie den Code abhängiger Komponenten aus den Lebenszyklusmethoden in die Komponenten selbst verschieben.
Das androidx.lifecycle Paket enthält Klassen und Schnittstellen, mit denen Sie lebenszyklusbezogene Komponenten erstellen können. Diese Komponenten können ihr Verhalten automatisch an den aktuellen Lebenszyklusstatus einer Aktivität oder eines Fragments anpassen.
Die meisten in Android Framework definierten App-Komponenten haben Lebenszyklen. Lebenszyklen werden vom Betriebssystem oder vom Framework-Code verwaltet, der in Ihrem Prozess ausgeführt wird. Sie sind ein wichtiger Bestandteil der Funktionsweise von Android und müssen von Ihrer Anwendung berücksichtigt werden. Andernfalls kann es zu Speicherlecks oder sogar zu Abstürzen der Anwendung kommen.
Angenommen, wir haben eine Aktivität, die den Gerätestandort auf dem Bildschirm anzeigt. 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
}
}
Obwohl dieses Beispiel gut aussieht, gibt es in einer echten App zu viele Aufrufe, die die UI und andere Komponenten als Reaktion auf den aktuellen Lebenszyklusstatus verwalten. Die Verwaltung mehrerer Komponenten führt dazu, dass eine beträchtliche Menge an
Code in Lebenszyklusmethoden wie onStart() und onStop platziert wird, was
die Wartung erschwert.
Außerdem gibt es keine Garantie, dass die Komponente gestartet wird, bevor die Aktivität oder das Fragment beendet wird. Das gilt insbesondere, wenn ein Vorgang mit langer Ausführungszeit ausgeführt werden muss, z. B. eine Konfigurationsprüfung in onStart. Dies kann zu einer Race-Bedingung führen, bei der die onStop() Methode vor dem 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 enthält Klassen und Schnittstellen, mit denen Sie
diese Probleme auf robuste 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 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
Lifecycle Klasse gesendet werden. Diese Ereignisse werden den Callback-Ereignissen in Aktivitäten und Fragmenten zugeordnet.
Bundesland
Der aktuelle Status der Komponente, die vom Lifecycle Objekt verfolgt wird.
Stellen Sie sich die Status 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, onStart, usw. ü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
LifecycleOwner Schnittstelle, die im folgenden Abschnitt erläutert wird.
LifecycleOwner
LifecycleOwner ist eine Schnittstelle mit einer einzigen Methode, die angibt, dass die Klasse
einen Lifecycle hat. Sie hat eine Methode, getLifecycle, die
von der Klasse implementiert werden muss. Wenn Sie stattdessen den Lebenszyklus eines gesamten
Anwendungsprozesses verwalten möchten, sehen Sie sich ProcessLifecycleOwner an.
Diese Schnittstelle abstrahiert das Eigentum an einem Lifecycle von einzelnen
Klassen wie Fragment und AppCompatActivity und ermöglicht
das Schreiben von Komponenten, die mit ihnen funktionieren. Jede benutzerdefinierte Anwendungsklasse kann
die LifecycleOwner Schnittstelle implementieren.
Komponenten, die DefaultLifecycleObserver implementieren, funktionieren nahtlos mit
Komponenten, die LifecycleOwner implementieren, da ein Eigentümer einen
Lebenszyklus bereitstellen kann, für den sich ein Beobachter registrieren kann.
Im Beispiel zur Standortverfolgung können wir die MyLocationListener Klasse
so gestalten, dass sie DefaultLifecycleObserver implementiert, und sie dann in der Methode onCreate() mit dem
Lifecycle der Aktivität initialisieren. Dadurch ist die Klasse MyLocationListener unabhängig. Das bedeutet, dass die Logik zum Reagieren auf Änderungen des Lebenszyklusstatus in MyLocationListener und nicht in der Aktivität deklariert wird. Wenn die einzelnen Komponenten ihre eigene Logik speichern, lässt sich die Logik von Aktivitäten und Fragmenten einfacher 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, bestimmte Callbacks nicht aufzurufen, wenn sich der Lifecycle
gerade 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 sollten wir diesen Callback niemals aufrufen.
Um diesen Anwendungsfall zu vereinfachen, können andere Objekte mit der Lifecycle Klasse den aktuellen Status abfragen.
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 Klasse LocationListener vollständig lebenszyklusbezogen. Wenn wir unseren LocationListener in einer anderen Aktivität oder einem anderen Fragment verwenden möchten, müssen wir ihn 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 die Verwendung von lebenszyklusbezogenen Komponenten. Ihre Bibliotheksclients können diese Komponenten einfach integrieren, ohne den Lebenszyklus auf Clientseite manuell verwalten zu müssen.
Benutzerdefinierten LifecycleOwner implementieren
Fragmente und Aktivitäten in der Support Library 26.1.0 und höher implementieren bereits
die LifecycleOwner Schnittstelle.
Wenn Sie eine benutzerdefinierte Klasse haben, die Sie zu einem LifecycleOwner,
machen 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 lebenszyklusbezogene Komponenten
- Halten Sie Ihre UI-Controller (Aktivitäten und Fragmente) so schlank wie möglich.
Sie sollten nicht versuchen, eigene Daten abzurufen. Verwenden Sie stattdessen ein
ViewModelund beobachten Sie einLiveData-Objekt, um die Änderungen in den Ansichten widerzuspiegeln. - Versuchen Sie, datengesteuerte UIs zu schreiben, bei denen die Aufgabe des UI-Controllers darin besteht, die Ansichten zu aktualisieren, wenn sich Daten ändern, oder Nutzeraktionen an das
ViewModelzu senden. - Platzieren Sie Ihre Datenlogik in der
ViewModelKlasse.ViewModelsollte als Verbindung zwischen Ihrem UI-Controller und dem Rest Ihrer App dienen. Seien Sie jedoch vorsichtig: Es ist nicht die Aufgabe vonViewModel's, Daten abzurufen (z. B. aus einem Netzwerk). Stattdessen sollteViewModeldie entsprechende Komponente aufrufen, um die Daten abzurufen, und das Ergebnis dann an den UI-Controller zurückgeben. - Verwenden Sie Datenbindung, um eine saubere Schnittstelle zwischen Ihren Ansichten und dem UI-Controller zu gewährleisten. 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 UI komplex ist, sollten Sie eine Presenter-Klasse erstellen, um UI-Änderungen zu verarbeiten. Das kann zwar mühsam sein, aber es kann das Testen Ihrer UI-Komponenten erleichtern.
- Vermeiden Sie es, in Ihrem
ViewModelauf einenView- oderActivity-Kontext zu verweisen. Wenn dasViewModeldie Aktivität überdauert (bei Konfigurationsänderungen), kommt es zu einem Speicherleck in der Aktivität und sie wird nicht ordnungsgemäß vom Garbage Collector entsorgt. - Verwenden Sie Kotlin-Coroutinen, um zeitaufwendige Aufgaben und andere Vorgänge zu verwalten, die asynchron ausgeführt werden können.
Anwendungsfälle für lebenszyklusbezogene Komponenten
Lebenszyklusbezogene Komponenten können die Verwaltung von Lebenszyklen in einer Vielzahl von Fällen erheblich erleichtern. Einige Beispiele:
- Zwischen groben und feinen Standortaktualisierungen wechseln. Verwenden Sie lebenszyklusbezogene Komponenten, um feine Standortaktualisierungen zu aktivieren, während Ihre Standort-App sichtbar ist, und wechseln Sie zu groben Aktualisierungen, wenn die App im Hintergrund ausgeführt wird.
LiveData, eine lebenszyklusbezogene Komponente, ermöglicht es Ihrer App, die UI automatisch zu aktualisieren, wenn der Nutzer den Standort ändert. - Videopufferung beenden und starten. Verwenden Sie lebenszyklusbezogene Komponenten, um die Videopufferung so schnell wie möglich zu starten, die Wiedergabe jedoch zu verzögern, bis die App vollständig gestartet wurde. Sie können lebenszyklusbezogene Komponenten auch verwenden, um die Pufferung zu beenden, wenn Ihre App beendet wird.
- Netzwerkverbindung starten und beenden. Verwenden Sie lebenszyklusbezogene Komponenten, um die Live-Aktualisierung (Streaming) von Netzwerkdaten zu aktivieren, während eine App im Vordergrund ausgeführt wird, und um sie automatisch zu pausieren, wenn die App in den Hintergrund wechselt.
- Animierte Drawables pausieren und fortsetzen. Verwenden Sie lebenszyklusbezogene Komponenten, um das Pausieren animierter Drawables zu verarbeiten, wenn die App im Hintergrund ausgeführt wird, und um Drawables fortzusetzen, nachdem die App im Vordergrund ausgeführt wird.
Ereignisse beim Beenden verarbeiten
Wenn ein Lifecycle zu einer AppCompatActivity oder
Fragment gehört, ändert sich der Status des Lifecycle's in CREATED und
das Ereignis ON_STOP wird gesendet, wenn onSaveInstanceState() der AppCompatActivity oder
Fragment's aufgerufen wird.
Wenn der Status eines Fragment oder AppCompatActivity's mit
onSaveInstanceState gespeichert wird, gilt die 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 löst FragmentManager eine Ausnahme aus, wenn die App eine FragmentTransaction ausführt, nachdem der Status gespeichert wurde. Weitere Informationen finden Sie unter commit() für
Details.
LiveData verhindert diesen Grenzfall standardmäßig, indem der Beobachter nicht aufgerufen wird, wenn der zugehörige Lifecycle des Beobachters nicht mindestens STARTED ist. Im Hintergrund wird isAtLeast() aufgerufen, bevor
entschieden wird, ob der Beobachter aufgerufen werden soll.
Leider wird die Methode AppCompatActivity's onStop()
nach onSaveInstanceState aufgerufen. Dadurch entsteht eine Lücke, in der Änderungen am UI-Status
nicht zulässig sind, der Lifecycle aber noch nicht in den
CREATED Status versetzt wurde.
Um dieses Problem zu vermeiden, wird der Status in der Klasse Lifecycle in Version beta2 und niedriger als CREATED markiert, ohne das Ereignis zu senden. So erhalten alle Code, die den aktuellen Status prüfen, den tatsächlichen Wert, obwohl das Ereignis erst gesendet wird, wenn onStop() vom System aufgerufen wird.
Leider hat diese Lösung zwei große Probleme:
- Auf API-Level 23 und niedriger speichert das Android-System den Status einer Aktivität auch dann, wenn sie teilweise von einer anderen Aktivität verdeckt wird. 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 UI-Status nicht geändert werden kann. - Jede Klasse, die ein ähnliches Verhalten wie die
LiveDataKlasse aufweisen soll, muss den Workaround implementieren, der in der Versionbeta 2und niedriger vonLifecyclebereitgestellt wird.
Zusätzliche Ressourcen
Weitere Informationen zum Verarbeiten von Lebenszyklen mit lebenszyklusbezogenen Komponenten finden Sie in den folgenden zusätzlichen Ressourcen.
Produktproben
- Sunflower, eine Demo-App, die Best Practices mit Architekturkomponenten demonstriert