Zarządzanie cyklami życia za pomocą komponentów uwzględniających cykl życia Element Jetpacka na Androida.
Komponenty uwzględniające cykl życia wykonują działania w odpowiedzi na zmianę stanu cyklu życia innego komponentu, np. aktywności lub fragmentu. Te komponenty pomagają tworzyć lepiej zorganizowany i często lżejszy kod, który jest łatwiejszy w utrzymaniu.
Typowym wzorcem jest implementowanie działań komponentów zależnych w metodach cyklu życia aktywności i fragmentów. Jednak taki schemat prowadzi do złej organizacji kodu i rozprzestrzeniania się błędów. Dzięki komponentom uwzględniającym cykl życia możesz przenieść kod komponentów zależnych z metod cyklu życia do samych komponentów.
Pakiet androidx.lifecycle
udostępnia klasy i interfejsy, które umożliwiają tworzenie komponentów świadomych cyklu życia. Są to komponenty, które mogą automatycznie dostosowywać swoje działanie do bieżącego stanu cyklu życia aktywności lub fragmentu.
Większość komponentów aplikacji zdefiniowanych w ramach Android Framework ma przypisane cykle życia. Cykl życia jest zarządzany przez system operacyjny lub kod frameworka działający w procesie. Są one podstawą działania Androida i Twoja aplikacja musi je uwzględniać. Niezastosowanie się do tej zasady może spowodować wyciek pamięci lub awarię aplikacji.
Załóżmy, że mamy aktywność, która wyświetla lokalizację urządzenia na ekranie. Typowa implementacja może wyglądać tak:
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 } }
Chociaż ten przykład wygląda dobrze, w rzeczywistej aplikacji kończy się na tym, że masz zbyt wiele wywołań, które zarządzają interfejsem użytkownika i innymi komponentami w odpowiedzi na bieżący stan cyklu życia. Zarządzanie wieloma komponentami powoduje, że znaczna część kodu znajduje się w metodach cyklu życia, takich jak onStart()
i onStop()
, co utrudnia ich utrzymanie.
Nie ma też gwarancji, że komponent zostanie uruchomiony przed zatrzymaniem aktywności lub fragmentu. Jest to szczególnie ważne, jeśli musimy wykonać długotrwałą operację, taką jak sprawdzenie konfiguracji w onStart()
. Może to spowodować stan wyścigu, w którym metoda onStop()
kończy działanie przed metodą onStart()
, co powoduje, że komponent pozostaje aktywny dłużej niż to konieczne.
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(); } }
Pakiet androidx.lifecycle
udostępnia klasy i interfejsy, które pomagają rozwiązywać te problemy w sposób odporny i odporny na błędy.
Cykl życia
Lifecycle
to klasa, która przechowuje informacje o stanie cyklu życia komponentu (np. aktywności lub fragmentu) i umożliwia innym obiektom obserwowanie tego stanu.
Lifecycle
korzysta z 2 głównych zbiorów do śledzenia stanu cyklu życia powiązanego komponentu:
- Wydarzenie
- Zdarzenia cyklu życia wysyłane z ramy i klasy
Lifecycle
. Te zdarzenia są mapowane na zdarzenia wywołania zwrotnego w aktywnościach i fragmentach. - Region
- Bieżący stan komponentu śledzonego przez obiekt
Lifecycle
.
Traktuj stany jako węzły grafu, a zdarzenia jako krawędzie między tymi węzłami.
Klasa może monitorować stan cyklu życia komponentu, implementując interfejs DefaultLifecycleObserver
i zastępując odpowiednie metody, takie jak onCreate
, onStart
itp. Następnie możesz dodać obserwatora, wywołując metodę addObserver()
klasy Lifecycle
i przekazując instancję obserwatora, jak w tym przykładzie:
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());
W przykładzie powyżej obiekt myLifecycleOwner
implementuje interfejs LifecycleOwner
, który jest opisany w sekcji poniżej.
LifecycleOwner
LifecycleOwner
to interfejs z jedną metodą, który oznacza, że zajęcia mają Lifecycle
. Zawiera jedną metodę getLifecycle()
, którą klasa musi zaimplementować.
Jeśli chcesz zarządzać cyklem życia całego procesu aplikacji, zapoznaj się z artykułem ProcessLifecycleOwner
.
Ten interfejs abstrakcyjnie traktuje własność Lifecycle
w poszczególnych klasach, takich jak Fragment
i AppCompatActivity
, i pozwala pisać komponenty, które z nimi współpracują. Interfejs LifecycleOwner
może być implementowany przez dowolną niestandardową klasę aplikacji.
Komponenty, które implementują
DefaultLifecycleObserver
działają bezproblemowo z komponentami, które implementują
LifecycleOwner
ponieważ właściciel może zapewnić cykl życia, który obserwator może zarejestrować, aby go obserwować.
W przypadku śledzenia lokalizacji możemy w klasie MyLocationListener
zaimplementować metodę DefaultLifecycleObserver
, a potem zainicjować ją za pomocą zmiennej Lifecycle
w metodzie onCreate()
. Dzięki temu klasa MyLocationListener
jest samowystarczalna, co oznacza, że logika reagowania na zmiany stanu cyklu życia jest deklarowana w klasie MyLocationListener
, a nie w aktywności. Dzięki temu, że poszczególne komponenty przechowują własną logikę, łatwiej jest zarządzać logiką działań i fragmentów.
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(); } }); } }
Typowym zastosowaniem jest unikanie wywoływania niektórych funkcji zwracających dane, jeśli Lifecycle
nie jest w danym momencie w dobrym stanie. Jeśli na przykład wywołanie zwrotne uruchamia transakcję fragmentu po zapisaniu stanu aktywności, spowoduje awarię, dlatego nigdy nie należy wywoływać tego wywołania zwrotnego.
Aby ułatwić to zadanie, klasa Lifecycle
umożliwia innym obiektom wysyłanie zapytań o obecny stan.
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 } }
Dzięki tej implementacji klasa LocationListener
jest w pełni świadoma cyklu życia. Jeśli chcemy użyć obiektu LocationListener
z innej aktywności lub fragmentu, musimy go zainicjować. Wszystkie operacje konfiguracji i demontażu są zarządzane przez zajęcia.
Jeśli biblioteka udostępnia klasy, które muszą współpracować z cyklem życia Androida, zalecamy używanie komponentów uwzględniających cykl życia. Klienci biblioteki mogą łatwo integrować te komponenty bez ręcznego zarządzania ich cyklem życia po stronie klienta.
Implementacja niestandardowego obiektu LifecycleOwner
Fragmenty i aktywności w Bibliotece wsparcia w wersji 26.1.0 i nowszych już implementują interfejs LifecycleOwner
.
Jeśli masz klasę niestandardową, którą chcesz użyć w miejscu klasy LifecycleOwner
, możesz użyć klasy LifecycleRegistry, ale musisz przekazać do niej zdarzenia, jak pokazano w tym przykładzie kodu:
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; } }
Sprawdzone metody dotyczące komponentów uwzględniających cykl życia
- Upewnij się, że kontrolery interfejsu użytkownika (aktywności i fragmenty) są jak najbardziej zwięzłe. Nie powinny one samodzielnie pozyskiwać danych, ale zamiast tego używać do tego metody
ViewModel
i obserwować obiektLiveData
, aby odzwierciedlić zmiany w widokach. - Spróbuj napisać interfejs użytkownika sterowany danymi, w którym zadaniem kontrolera interfejsu jest aktualizowanie widoków w miarę zmian danych lub powiadamianie
ViewModel
o działaniach użytkownika. - Umieść logikę danych w klasie
ViewModel
.ViewModel
powinna pełnić funkcję łącznika między kontrolerem interfejsu użytkownika a resztą aplikacji. Pamiętaj jednak, że nie jest odpowiedzialna za pobieranie danych (np. z sieci).ViewModel
Zamiast tegoViewModel
powinna wywołać odpowiedni komponent, aby pobrać dane, a potem przekazać wynik do kontrolera interfejsu użytkownika. - Użyj wiązania danych, aby zachować przejrzystość interfejsu między widokami a kontrolerem interfejsu użytkownika. Dzięki temu możesz tworzyć bardziej deklaracyjne widoki i minimalizować kod aktualizacji, który musisz napisać w swoich aktywnościach i fragmentach. Jeśli wolisz to zrobić w języku programowania Java, użyj biblioteki takiej jak Butter Knife, aby uniknąć szablonowego kodu i uzyskać lepszą abstrakcję.
- Jeśli interfejs jest złożony, rozważ utworzenie klasy prezenter, która będzie obsługiwać modyfikacje interfejsu. Może to być pracochłonne, ale ułatwia testowanie komponentów interfejsu użytkownika.
- Unikaj odwoływania się do kontekstu
View
lubActivity
wViewModel
. JeśliViewModel
przetrwa aktywność (w przypadku zmian konfiguracji), Twoja aktywność wycieka i nie jest prawidłowo usuwana przez zbieracza. - Do zarządzania długotrwałymi zadaniami i innymi operacjami, które mogą być wykonywane asynchronicznie, używaj korobon w Kotlinie.
Przypadki użycia komponentów uwzględniających cykl życia
Komponenty uwzględniające cykl życia mogą znacznie ułatwić zarządzanie cyklami życia w różnych przypadkach. Oto kilka przykładów:
- Przełączanie się między ogólnymi a szczegółowymi aktualizacjami lokalizacji. Używaj komponentów uwzględniających cykl życia, aby umożliwić szczegółowe aktualizacje lokalizacji, gdy aplikacja jest widoczna, i przełączać się na aktualizacje ogólne, gdy aplikacja działa w tle.
LiveData
, komponent uwzględniający cykl życia, pozwala aplikacji automatycznie aktualizować interfejs użytkownika, gdy ten zmienia lokalizację. - Zatrzymywanie i rozpoczynanie buforowania filmu. Używaj komponentów uwzględniających cykl życia, aby jak najszybciej rozpocząć buforowanie wideo, ale opóźnić odtwarzanie do czasu pełnego uruchomienia aplikacji. Możesz też użyć komponentów uwzględniających cykl życia, aby zakończyć buforowanie, gdy aplikacja zostanie zniszczona.
- inicjowanie i zatrzymywanie połączenia sieciowego; Używaj komponentów uwzględniających cykl życia, aby umożliwić aktualizowanie na żywo (przesyłanie strumieniowe) danych sieciowych, gdy aplikacja jest na pierwszym planie, a także automatyczne wstrzymywanie, gdy aplikacja przechodzi do działania w tle.
- Wstrzymywanie i wznawianie animowanych obiektów do rysowania. Używaj komponentów uwzględniających cykl życia, aby obsługiwać wstrzymywanie animowanych obiektów rysowanych, gdy aplikacja jest w tle, oraz wznawianie ich rysowania, gdy aplikacja jest na pierwszym planie.
Obsługa zdarzeń zatrzymania
Gdy Lifecycle
należy do AppCompatActivity
lub Fragment
, stan Lifecycle
zmienia się na CREATED
, a zdarzenie ON_STOP
jest wysyłane, gdy wywoływane jest AppCompatActivity
lub Fragment
.onSaveInstanceState()
Gdy stan Fragment
lub AppCompatActivity
jest zapisywany za pomocą funkcji onSaveInstanceState()
, jego interfejs jest uważany za niezmienny, dopóki nie zostanie wywołana funkcja ON_START
. Próba zmodyfikowania interfejsu po zapisaniu stanu prawdopodobnie spowoduje niespójności w stanie nawigacji aplikacji. Dlatego funkcja FragmentManager
wyrzuca wyjątek, jeśli aplikacja uruchomi funkcję FragmentTransaction
po zapisaniu stanu. Więcej informacji znajdziesz na stronie commit()
.
LiveData
zapobiega temu brzegowemu przypadkowi, nie wywołując obserwatora, jeśli powiązany z nim Lifecycle
nie jest co najmniej STARTED
.
W tle wywołuje ona funkcję isAtLeast()
, zanim zdecyduje się wywołać swojego obserwatora.
Niestety metoda onStop()
klasy AppCompatActivity
jest wywoływana po
onSaveInstanceState()
, co powoduje, że zmiany stanu interfejsu nie są dozwolone, ale obiekt Lifecycle
nie został jeszcze przeniesiony do stanu CREATED
.
Aby uniknąć tego problemu, klasa Lifecycle
w wersji beta2
i niżej oznacza stan jako CREATED
bez wysyłania zdarzenia, dzięki czemu każdy kod sprawdzający bieżący stan otrzymuje rzeczywistą wartość, mimo że zdarzenie nie jest wysyłane, dopóki system nie wywoła funkcji onStop()
.
Niestety, to rozwiązanie ma 2 główne problemy:
- Na poziomie interfejsu API 23 lub niższym system Android zapisuje stan aktywności, nawet jeśli jest on częściowo objęty inną aktywnością. Innymi słowy, system Androida wywołuje funkcję
onSaveInstanceState()
, ale niekoniecznieonStop()
. Powoduje to potencjalnie długi przedział czasu, w którym obserwator nadal uważa, że cykl życia jest aktywny, mimo że stanu interfejsu nie można zmodyfikować. - Każda klasa, która ma działać podobnie do klasy
LiveData
, musi zaimplementować obejście dostępne w wersjiLifecycle
beta 2
lub niższej.
Dodatkowe materiały
Aby dowiedzieć się więcej o obsługiwaniu cykli życia za pomocą komponentów uwzględniających cykl życia, zapoznaj się z tymi dodatkowymi materiałami.
Próbki
- Sunflower, aplikacja demonstracyjna prezentująca sprawdzone metody korzystania z Architecture Components
Ćwiczenia z programowania
Blogi
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy obsługa JavaScript jest wyłączona
- Omówienie LiveData
- Używanie współbieżnych funkcji Kotlina z komponentami uwzględniającymi cykl życia
- Moduł zapisanego stanu dla ViewModel