Obsługa cykli życia dzięki komponentom uwzględniającym cykl życia Zawiera Android Jetpack.

Komponenty uwzględniające cykl życia podejmują działania w odpowiedzi na zmianę stanu cyklu życia innego komponentu, np. działań i fragmentów. Te pozwalają tworzyć lepiej zorganizowany i często prostszy kod, jest łatwiejszy w utrzymaniu.

Popularnym wzorcem jest implementacja działań zależnych komponentów w funkcji metod cyklu życia działań i fragmentów. Ten schemat prowadzi jednak do niewłaściwa organizacja kodu i rozprzestrzenianie się błędów. Za pomocą uwzględniających cykl życia, możesz przenieść kod powiązanych komponentów w metodach cyklu życia i w samych komponentach.

androidx.lifecycle udostępnia klasy i interfejsy, które pozwalają przystosowywać się do cyklu życia. czyli komponentów, które automatycznie dostosowują swoje na podstawie bieżącego stanu cyklu życia działania lub fragmentu.

Większość komponentów aplikacji zdefiniowanych w Android Framework powiązane z nimi cykle życia. Cyklami życia zarządza system operacyjny lub z kodem platformy działającym w Twoim procesie. Są kluczowe dla działania Androida a aplikacja musi je respektować. Jeśli tego nie zrobisz, mogą wystąpić wycieki pamięci lub nawet awarii aplikacji.

Wyobraź sobie, że mamy aktywność, która pokazuje lokalizację urządzenia na ekranie. O typowa implementacja może być taka:

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 fragment wygląda dobrze, w prawdziwej aplikacji pojawia się za dużo wywołania, które zarządzają interfejsem użytkownika i innymi komponentami w odpowiedzi na bieżący stan w kontekście ich cyklu życia. Zarządzanie wieloma komponentami niesie ze sobą sporą część w metodach cyklu życia, takich jak onStart() czy onStop(), co utrudnia ich utrzymanie.

Co więcej, nie ma gwarancji, że komponent rozpocznie się przed działaniem lub zatrzymano fragment. Dotyczy to szczególnie sytuacji, gdy musimy długo trwającą operację, taką jak sprawdzenie konfiguracji w onStart(). Może to spowodować warunek wyścigu, w którym metoda onStop() zakończy działanie przed onStart(), przez co komponent będzie żył dłużej niż trwa niezbędną.

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();
    }
}

androidx.lifecycle udostępnia klasy i interfejsy, które ułatwiają rozwiązywanie tych problemów wytrzymały i izolowany sposób.

Cykl życia

Lifecycle to zajęcia który zawiera informacje o stanie cyklu życia komponentu (np. działania lub fragmentu) i pozwala innym obiektom obserwować ten stan.

Lifecycle używa 2 głównych wyliczenia, aby śledzić stan cyklu życia powiązanego z nim komponentu:

Wydarzenie
Zdarzenia cyklu życia wysyłane z platformy i platformy Zajęcia: Lifecycle. Te są mapowane na zdarzenia wywołania zwrotnego w aktywnościach i fragmentach.
Region
Bieżący stan komponentu śledzonego przez Lifecycle.
Diagram stanów cyklu życia
. Rysunek 1. Stany i zdarzenia, które składają się na aktywność na Androidzie cykl życia
.

Pomyśl o stanach jako o węzłach grafu, a zdarzeniach – jako krawędziach między dla tych węzłów.

Klasa może monitorować stan cyklu życia komponentu, implementując DefaultLifecycleObserver i zastępowanie odpowiednich metod, takich jak onCreate, onStart itp. Następnie możesz dodać obserwatora, wywołując funkcję addObserver(). Lifecycle i przekazywanie instancji obserwatora, jak w przykładzie poniżej przykład:

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 powyższym przykładzie obiekt myLifecycleOwner implementuje funkcję LifecycleOwner. i został objaśniony w następnej sekcji.

LifecycleOwner

LifecycleOwner to interfejsu jednej metody, który wskazuje, że klasa ma Lifecycle Zawiera jedną , getLifecycle() który musi zostać wdrożony przez klasę. Jeśli próbujesz zarządzać cyklem życia całej aplikacji Więcej informacji znajdziesz w artykule ProcessLifecycleOwner

Ten interfejs przedstawia abstrakcyjne prawa własności Lifecycle od osoby fizycznej takich jak Fragment i AppCompatActivity, oraz umożliwia pisanie komponentów, które z nimi współpracować. Każda niestandardowa klasa aplikacji może implementować LifecycleOwner za pomocą prostego interfejsu online.

Komponenty, które implementują DefaultLifecycleObserver współdziałają z komponentami, które stosują LifecycleOwner ponieważ właściciel może określić cykl życia, w którym obserwator może się zarejestrować. zegarka.

W przypadku przykładu śledzenia lokalizacji możemy utworzyć klasę MyLocationListener zastosuj DefaultLifecycleObserver a następnie zainicjuj ją kodem aktywności Lifecycle w metodzie onCreate(). Dzięki temu funkcja MyLocationListener, co oznacza, że logika w reakcji na zmiany stanu cyklu życia jest zadeklarowany w zasadzie MyLocationListener. danej aktywności. To, ż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 przypadkiem użycia jest unikanie wywoływania określonych wywołań zwrotnych, jeśli Lifecycle nie jest w dobrym w tym stanie. Jeśli na przykład wywołanie zwrotne uruchamia transakcję dotyczącą fragmentu po zapis stanu aktywności spowodowałoby wypadek, więc nigdy aby wywołać to wywołanie zwrotne.

Aby ułatwić ten przypadek użycia, Lifecycle klasa zezwala lub inne obiekty, aby wysłać zapytanie dotyczące 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
    }
}

Przy tej implementacji nasza klasa LocationListener jest całkowicie z uwzględnieniem cyklu życia. Jeśli musimy użyć karty LocationListener w innej aktywności lub fragmentu, wystarczy go zainicjować. Cała konfiguracja i wymiana Operacje są zarządzane przez samą klasę.

Jeśli biblioteka udostępnia zajęcia, które muszą być dostosowane do cyklu życia Androida, zalecamy korzystanie z komponentów uwzględniających cykl życia. Twoi bibliotekarze mogą łatwo zintegrować te komponenty bez ręcznego zarządzania cyklem życia po stronie klienta.

Implementowanie niestandardowego właściciela cyklu życia

Fragmenty i działania w bibliotece pomocy w wersji 26.1.0 i nowszych są już implementowane LifecycleOwner za pomocą prostego interfejsu online.

Jeśli masz klasę niestandardową, w której chcesz utworzyć LifecycleOwner, Ty może użyć funkcji Rejestr cyklu życia , ale musisz przekazać do nich zdarzenia, jak pokazano poniżej przykładowy kod:

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

  • Kontrolery interfejsu (działania i fragmenty) powinny być jak najbardziej oszczędne. Ta nie powinni próbować pozyskiwać własnych danych; zamiast tego użyj ViewModel, aby to zrobić, i obserwuj LiveData w celu odzwierciedlenia zmian w widokach.
  • Spróbuj napisać interfejsy oparte na danych, w których kontroler UI odpowiada za aktualizować widoki w miarę zmian danych lub powiadamiać użytkowników o działaniach ViewModel
  • Uwzględnij logikę w zakresie danych w ViewModel. Aplikacja ViewModel powinna wyświetlać reklamy jako połączenie między kontrolerem UI a resztą aplikacji. Bądź jednak nie jest tak użytkownika ViewModel za pobieranie danych (na przykład z sieci). Zamiast tego: Aplikacja ViewModel powinna wywołać odpowiedni komponent do pobrania danych, a potem przekaż wynik z powrotem do za pomocą kontrolera UI.
  • Użyj funkcji Data Binding, aby utrzymać przejrzysty interfejs między widokami a kontrolerem UI. Dzięki temu możesz: zwiększ deklaratowość i zminimalizuj kod aktualizacji, zapisywać w swoich działaniach i fragmentach. Jeśli wolisz to zrobić w Javie języka programowania, użyj biblioteki takiej jak Nóż obudowy – unikanie powtarzalnych elementów i mają większą abstrakcję.
  • Jeśli Twój interfejs jest złożony, rozważ utworzenie prowadzący do obsługi modyfikacji interfejsu użytkownika. Może to być pracochłonne, ale ułatwiają testowanie komponentów interfejsu.
  • Unikaj odniesień do: View lub Activity w ViewModel. Jeśli aktywność ViewModel przestaje być aktywna (w przypadku zmiany konfiguracji), dane o aktywności wyciekają i nie są odpowiednio usuwane przez śmieci.
  • Do zarządzania można używać współrzędnych Kotlin długotrwałe zadania i inne operacje, 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 różnych przypadkach. Oto kilka przykładów:

  • Przełączanie między przybliżoną i szczegółową aktualizacją lokalizacji. Używaj z uwzględnieniem cyklu życia komponentów, aby umożliwić szczegółową aktualizację lokalizacji podczas aplikacja do określania lokalizacji jest widoczna i przełącza się na bardziej szczegółową aktualizację, gdy aplikacja jest w tle. LiveData, komponent uwzględniający cykl życia, umożliwia aplikacji automatyczne aktualizowanie interfejsu użytkownika po zmianie użytkownika lokalizacji.
  • Zatrzymanie i rozpoczęcie buforowania filmu. Zacznij od komponentów dopasowanych do cyklu życia buforowanie filmu tak szybko, jak to możliwe, z opóźnieniem odtwarzania do momentu, gdy aplikacja będzie całkowicie rozpoczęto. Aby zakończyć buforowanie, możesz też użyć komponentów uwzględniających cykl życia gdy Twoja aplikacja zostanie zniszczona.
  • Zaczynam i zatrzymuję połączenie sieciowe. Wykorzystaj komponenty uwzględniające cykl życia, aby: włącz aktualizowanie na żywo (strumieniowe) danych sieciowych, gdy aplikacja jest na pierwszym planie, a także automatycznie wstrzymywać działanie aplikacji, w tle.
  • Wstrzymywanie i wznawianie animowanych elementów możliwych do rysowania. Wykorzystaj komponenty uwzględniające cykl życia, aby: uchwyt wstrzymywania animowanych elementów rysunkowych, gdy aplikacja działa w tle, wznawiania elementów rysowanych po uruchomieniu aplikacji na pierwszym planie.

Obsługa w przypadku zdarzeń zatrzymania

Gdy Lifecycle należy do AppCompatActivity lub Fragment, Lifecycle stan zmienia się na CREATED i ON_STOP jest wysyłane, gdy AppCompatActivity lub Fragment onSaveInstanceState() .

Gdy stan urządzenia Fragment lub AppCompatActivity jest zapisywany w onSaveInstanceState(), to UI jest uważany za niezmienny do ON_START jest . Próba zmodyfikowania interfejsu użytkownika po zapisaniu stanu prawdopodobnie spowoduje niespójności w stanie nawigacji aplikacji, dlatego FragmentManager zgłasza wyjątek, jeśli aplikacja uruchomi FragmentTransaction po zapisaniu stanu. Zobacz commit(), aby wyświetlić szczegóły.

LiveData sprawia, że ten przypadek nie jest już gotowy, ponieważ przed wywołaniem obserwatora, jeśli powiązany z obserwatorem jest Lifecycle nie jest przynajmniej STARTED. Materiały zza kulis wzywają isAtLeast() przed podjęciem decyzji o wywołaniu jego obserwatora.

Metoda onStop() metody AppCompatActivity jest wywoływana po onSaveInstanceState(), co zostawia lukę, w której zmiany stanu UI są niedozwolone, ale Aplikacja Lifecycle nie została jeszcze przeniesiona do CREATED stanu.

Aby zapobiec temu problemowi, klasa Lifecycle w wersji beta2 i zmniejsz oznaczenie stanu jako CREATED bez wysyłania zdarzenia, dzięki czemu kod sprawdzający bieżący otrzymuje rzeczywistą wartość, mimo że zdarzenie nie jest wysyłane do czasu wywołania funkcji onStop() przez system.

Niestety, to rozwiązanie ma dwa główne problemy:

  • Na poziomie API 23 i niższym system Android zapisuje stan nawet jeśli jest ona częściowo objęta inną aktywnością. W innym słów, system Android nazywa onSaveInstanceState() ale niekoniecznie wywołuje onStop(). Potencjalnie to długi przedział czasu, w którym obserwator nadal uważa, że cykl życia jest aktywny chociaż nie można zmienić jego stanu UI.
  • Każda klasa, która chce udostępnić podobne zachowanie Klasa LiveData musi wdrożyć obejście opisane przez Lifecycle w wersji beta 2 i starszych.
.

Dodatkowe materiały

Aby dowiedzieć się więcej o obsłudze cykli życia za pomocą komponentów uwzględniających cykl życia, zapoznaj się z tymi dodatkowymi materiałami.

Próbki

Ćwiczenia z programowania

Blogi

. .