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

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