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.
Diagramm der Lebenszyklusstatus
Abbildung 1. Status und Ereignisse im Android-Aktivitätslebenszyklus

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 ein LiveData-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 von ViewModel, Daten abzurufen (z. B. aus einem Netzwerk). Stattdessen sollte ViewModel 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 einen View- oder Activity-Kontext. Wenn ViewModel 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 unbedingt onStop(). 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 von Lifecycle Version beta 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

Codelabs

Blogs