Obsługa cyklu życia za pomocą komponentów uwzględniających cykl życia Część stanowiąca część Androida Jetpack.
Komponenty uwzględniające cykl życia wykonują działania w odpowiedzi na zmianę stanu cyklu życia innego komponentu, np. aktywności lub fragmentów. Te komponenty pomagają w tworzeniu lepiej uporządkowanego, a często także lżejszego kodu, który jest łatwiejszy w utrzymaniu.
Częstym wzorcem jest implementacja działań zależnych komponentów w metodach cyklu życia działań i fragmentów. Taki schemat prowadzi jednak do kiepskiej organizacji kodu i liczby błędów. Korzystając z komponentów uwzględniających cykl życia, możesz przenieść kod komponentów zależnych z metod cyklu życia do samych komponentów.
Pakiet androidx.lifecycle
zawiera klasy i interfejsy umożliwiające tworzenie komponentów zorientowanych na cykl życia, czyli komponentów mogących automatycznie dostosowywać swoje działanie na podstawie bieżącego stanu cyklu życia aktywności lub fragmentu.
Większość komponentów aplikacji zdefiniowanych w Android Framework ma przypisane cykle życia. Cyklami życia zarządza system operacyjny lub uruchamiany w nim kod platformy. Są kluczem do działania Androida a Twoje aplikacje muszą je respektować. W przeciwnym razie mogą wystąpić wycieki pamięci, a nawet awarie aplikacji.
Wyobraź sobie, że mamy aktywność, która pokazuje 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 } }
Mimo że ten przykład wygląda dobrze, w rzeczywistej aplikacji pojawia się w takiej sytuacji zbyt wiele wywołań zarządzających interfejsem i innymi komponentami w odpowiedzi na bieżący stan cyklu życia aplikacji. Zarządzanie wieloma komponentami powoduje umieszczenie znacznej części kodu w metodach cyklu życia, np. onStart()
i onStop()
, co utrudnia ich obsługę.
Nie ma też gwarancji, że komponent uruchomi się przed zatrzymaniem działania lub fragmentu. Zwłaszcza wtedy, gdy musimy wykonać długotrwałą operację, taką jak sprawdzenie konfiguracji w onStart()
. Może to spowodować warunek wyścigu, w którym metoda onStop()
kończy się przed zdarzeniem onStart()
, przez co komponent utrzymuje się dłużej, niż jest to potrzebne.
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
zawiera klasy i interfejsy, które pomagają rozwiązywać te problemy w odporny, odizolowany sposób.
Cykl życia
Lifecycle
to klasa przechowująca informacje o stanie cyklu życia komponentu (np. aktywność lub fragment) i pozwala innym obiektom obserwować ten stan.
Lifecycle
używa 2 głównych wyliczeń do śledzenia stanu cyklu życia powiązanego komponentu:
- Wydarzenie
- Zdarzenia cyklu życia wysyłane z platformy i klasy
Lifecycle
. Zdarzenia te są mapowane na zdarzenia wywołania zwrotnego w działaniach i fragmentach. - Region
- Bieżący stan komponentu śledzonego przez obiekt
Lifecycle
.
Stany są węzłami wykresu, a zdarzenia – jak krawędziami między tymi węzłami.
Klasa może monitorować stan cyklu życia komponentu przez wdrożenie metody DefaultLifecycleObserver
i zastępowanie odpowiednich metod, takich jak onCreate
, onStart
itp. Następnie możesz dodać obserwatora, wywołując metodę addObserver()
klasy Lifecycle
i przekazując instancję obserwatora, tak 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 został opisany w następnej sekcji.
Właściciel cyklu życia
LifecycleOwner
to interfejs pojedynczej metody, który wskazuje, że klasa ma Lifecycle
. Ma jedną metodę getLifecycle()
, którą musi wdrożyć klasa.
Jeśli zamiast tego chcesz zarządzać cyklem życia całego procesu aplikacji, przeczytaj artykuł ProcessLifecycleOwner
.
Interfejs ten wyodrębnia własność elementu Lifecycle
z poszczególnych klas, takich jak Fragment
czy AppCompatActivity
, i umożliwia pisanie komponentów, które z nimi współpracują. Każda niestandardowa klasa aplikacji może implementować interfejs LifecycleOwner
.
Komponenty implementujące DefaultLifecycleObserver
bezproblemowo współpracują z komponentami implementującymi LifecycleOwner
, ponieważ właściciel może zapewnić cykl życia, który może zarejestrować obserwator.
Na potrzeby przykładu śledzenia lokalizacji możemy wprowadzić klasę MyLocationListener
wdrożoną DefaultLifecycleObserver
, a następnie zainicjować ją za pomocą metody Lifecycle
aktywności w metodzie onCreate()
. Dzięki temu klasa MyLocationListener
jest samowystarczająca, co oznacza, że logika reagowania na zmiany stanu cyklu życia jest deklarowana w MyLocationListener
, a nie w aktywności. Przechowywanie własnych mechanizmów logicznych przez poszczególne komponenty
ułatwia zarządzanie działaniami i logiką 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 przypadkiem użycia jest unikanie wywoływania niektórych wywołań zwrotnych, jeśli element Lifecycle
nie jest obecnie w dobrym stanie. Jeśli na przykład wywołanie zwrotne uruchomi transakcję dotyczącą fragmentu po zapisaniu stanu aktywności, spowoduje to awarię i nie będzie trzeba wywoływać tego wywołania zwrotnego.
Aby ułatwić ten przypadek użycia, klasa Lifecycle
umożliwia innym obiektom wysyłanie zapytań dotyczących bieżącego stanu.
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 takiej implementacji nasza klasa LocationListener
jest w pełni świadoma cyklu życia. Jeśli musimy użyć obiektu LocationListener
z innej aktywności lub fragmentu, wystarczy go zainicjować. Wszystkimi operacjami konfiguracji i demontażu zarządza sama klasa.
Jeśli biblioteka udostępnia zajęcia, które muszą być zgodne z cyklem życia Androida, zalecamy korzystanie z komponentów uwzględniających cykl życia. Klienty biblioteki mogą łatwo zintegrować te komponenty bez ręcznego zarządzania cyklem życia po stronie klienta.
Wdrażanie niestandardowego właściciela cyklu życia
Fragmenty i działania w bibliotece pomocy w wersji 26.1.0 i nowszych implementują już interfejs LifecycleOwner
.
Jeśli masz klasę niestandardową, którą chcesz utworzyć LifecycleOwner
, możesz użyć klasy LifecycleRegistry, ale musisz przekazywać do niej zdarzenia, tak jak w tym przykładowym kodzie:
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
- Zadbaj o jak najmniejszą ilość kontrolerów interfejsu (aktywności i fragmentów). Nie należy próbować pozyskać własnych danych. W tym celu użyj narzędzia
ViewModel
i obserwuj obiektLiveData
, aby odzwierciedlić zmiany w widokach danych. - Spróbuj pisać interfejsy użytkownika oparte na danych, w których kontroler UI odpowiada za aktualizowanie widoków po zmianie danych lub powiadamiaj o działaniach użytkowników z powrotem do
ViewModel
. - Przenieś logikę danych do klasy
ViewModel
. UsługaViewModel
powinna służyć jako łącznik między kontrolerem UI a resztą aplikacji. Uważaj jednak, że za pobieranie danych (np. z sieci) nie odpowiada usługaViewModel
. Zamiast tegoViewModel
powinien wywołać odpowiedni komponent, aby pobrać dane, a następnie przekazać wynik z powrotem do kontrolera interfejsu. - Używaj powiązywania danych, aby utrzymać czysty interfejs między widokami i kontrolerem interfejsu. Pozwala to zwiększyć deklaratywne widoki widoków i zminimalizować ilość kodu aktualizacji, który trzeba pisać w działaniach i fragmentach. Jeśli wolisz robić to w języku programowania Java, użyj biblioteki takiej jak butter Knife, by uniknąć powtarzającego się kodu i uzyskać lepszą abstrakcję.
- Jeśli Twój interfejs użytkownika jest złożony, rozważ utworzenie klasy Prezentera do obsługi modyfikacji interfejsu. Może to być pracochłonne, ale może ułatwić testowanie komponentów UI.
- Unikaj odwoływania się do kontekstu
View
lubActivity
wViewModel
. Jeśli elementViewModel
przetrwa działanie (w przypadku zmiany konfiguracji), aktywność wycieknie i nie zostanie poprawnie usunięta przez kolektor śmieci. - Używaj korekty Kotlin do zarządzania długotrwałymi zadaniami i innymi operacjami, które mogą być uruchamiane asynchronicznie.
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 wielu przypadkach. Oto kilka przykładów:
- Przełączanie się między szczegółowym i precyzyjnym aktualizowaniem lokalizacji. Korzystaj z komponentów uwzględniających cykl życia, aby umożliwić szczegółową aktualizację lokalizacji, gdy aplikacja z lokalizacją jest widoczna, a także przełączyć się na aktualizacje o dużej ziarnistości, gdy aplikacja działa w tle.
LiveData
– komponent identyfikujący cykl życia, umożliwia aplikacji automatyczne aktualizowanie interfejsu, gdy użytkownik zmieni lokalizację. - Zatrzymuję i uruchamiam buforowanie filmu. Użyj komponentów uwzględniających cykl życia, aby jak najszybciej rozpocząć buforowanie filmu, ale opóźnić odtwarzanie do momentu pełnego uruchomienia aplikacji. Możesz też użyć komponentów uwzględniających cykl życia, aby wyłączyć buforowanie po zniszczeniu aplikacji.
- Uruchamiam i zatrzymuję połączenia sieciowe. Użyj komponentów uwzględniających cykl życia, aby umożliwić aktualizowanie na żywo (strumieniowe) danych sieciowych, gdy aplikacja działa na pierwszym planie, oraz automatyczne wstrzymywanie jej działania w tle.
- Wstrzymywanie i wznawianie animowanych elementów rysowanych. Za pomocą komponentów uwzględniających cykl życia możesz wstrzymywać animowane elementy rysowane, gdy aplikacja działa w tle, i wznawiać te elementy, gdy aplikacja działa na pierwszym planie.
Obsługa zdarzeń zatrzymania
Gdy element Lifecycle
należy do AppCompatActivity
lub Fragment
, stan elementu Lifecycle
zmienia się na CREATED
, a zdarzenie ON_STOP
jest wysyłane po wywołaniu zdarzenia AppCompatActivity
lub Fragment
onSaveInstanceState()
.
Gdy stan elementu Fragment
lub AppCompatActivity
jest zapisywany za pomocą onSaveInstanceState()
, interfejs jest uznawany za niezmienny, dopóki nie zostanie wywołany element ON_START
. Próba zmodyfikowania interfejsu po zapisaniu stanu prawdopodobnie spowoduje niespójności w stanie nawigacji w Twojej aplikacji, dlatego FragmentManager
zgłasza wyjątek, jeśli po zapisaniu stanu aplikacja uruchamia wyjątek FragmentTransaction
. Aby dowiedzieć się więcej, wejdź na commit()
.
LiveData
zapobiega wyjściu z urządzenia, powstrzymując się od wywoływania jego obserwatora, jeśli powiązane z nim Lifecycle
nie wynoszą przynajmniej STARTED
.
W tle wywołuje metodę isAtLeast()
, zanim zdecyduje się wywołać swojego obserwatora.
Niestety metoda onStop()
w AppCompatActivity
jest wywoływana po
onSaveInstanceState()
, co powoduje lukę, w wyniku której zmiany stanu interfejsu są niedozwolone, ale element Lifecycle
nie został jeszcze przeniesiony do stanu CREATED
.
Aby zapobiec temu problemowi, klasa Lifecycle
w wersji beta2
i niższym oznacza stan jako CREATED
bez wysyłania zdarzenia. Dzięki temu każdy kod, który sprawdza bieżący stan, otrzymuje wartość rzeczywistą, mimo że zdarzenie nie jest wysyłane, dopóki system nie zostanie wywołany przez system onStop()
.
Niestety w tym rozwiązaniu występują 2 główne problemy:
- W przypadku interfejsu API na poziomie 23 i niższym system Android faktycznie zapisuje stan działania, nawet jeśli częściowo jest on objęty inną aktywnością. Inaczej mówiąc, system Android wywołuje metodę
onSaveInstanceState()
, ale niekoniecznieonStop()
. Powoduje to potencjalnie długi przedział czasu, w którym obserwator w dalszym ciągu uważa, że cykl życia jest aktywny, mimo że nie można zmienić jego stanu interfejsu. - Każda klasa, która chce zaprezentować podobne działanie klasie
LiveData
, musi stosować obejście dostępne wLifecycle
w wersjibeta 2
i starszej.
Dodatkowe materiały
Aby dowiedzieć się więcej o obsłudze cyklu życia z użyciem komponentów uwzględniających cykl życia, zapoznaj się z dodatkowymi materiałami.
Próbki
- Podstawowy przykład z komponentami architektury Androida
- Słonecznik – aplikacja demonstracyjna przedstawiająca sprawdzone metody dotyczące komponentów architektury
Ćwiczenia z programowania
Blogi
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Omówienie LiveData
- Używanie współprogramów Kotlin z komponentami uwzględniającymi cykl życia
- Moduł zapisanego stanu dla modelu ViewModel