Cykl życia aktywności

Gdy użytkownik porusza się po aplikacji, wychodzi z niej i wraca do niej, jej instancje Activity przechodzą przez różne stany w swoim cyklu życia. Klasa Activity udostępnia wiele wywołań zwrotnych, które informują działanie o zmianie stanu, o tym, że system tworzy, zatrzymuje lub wznawia działanie albo niszczy proces, w którym ona występuje.

W ramach metod wywołania zwrotnego w cyklu życia możesz zadeklarować, jak działa aktywność, gdy użytkownik ją opuszcza i ponownie wchodzi do niej. Jeśli na przykład tworzysz odtwarzacz strumieniowy, możesz wstrzymać odtwarzanie filmu i zakończyć połączenie sieciowe, gdy użytkownik przełączy się na inną aplikację. Po powrocie użytkownika możesz ponownie połączyć się z siecią i pozwolić mu wznowić odtwarzanie filmu w tym samym miejscu.

Każde wywołanie zwrotne umożliwia wykonanie określonej czynności odpowiedniej dla danej zmiany stanu. Właściwa praca we właściwym czasie i prawidłowa obsługa przejść sprawiają, że aplikacja jest bardziej niezawodna i wydajna. Odpowiednia implementacja wywołań zwrotnych cyklu życia może pomóc Twojej aplikacji uniknąć:

  • ulega awarii, jeśli użytkownik odbiera połączenie telefoniczne lub przełącza się na inną aplikację podczas korzystania z Twojej aplikacji.
  • Zużywanie cennych zasobów systemowych, gdy użytkownik ich nie używa.
  • utratę postępów użytkownika, jeśli opuści aplikację i wróci do niej później;
  • Awarie lub utratę postępów użytkownika, gdy ekran zostanie obrócony między orientacją poziomą a pionową.

W tym dokumencie szczegółowo opisujemy cykl życia aktywności. Dokument rozpoczyna się od opisania paradygmatu cyklu życia. Następnie objaśniamy poszczególne wywołania zwrotne: co dzieje się wewnętrznie podczas ich wykonywania i co trzeba w nich zaimplementować.

Następnie krótko omawiamy związek między stanem działania a bezpieczeństwem danego procesu na jego śmierć w systemie. Na koniec omówiono kilka tematów związanych z przechodzeniem między stanami aktywności.

Informacje o obsłudze cyklów życia, w tym wskazówki dotyczące sprawdzonych metod, znajdziesz w artykułach Obsługa cyklu życia przy użyciu komponentów odzwierciedlających cykl życia i Zapisywanie stanów interfejsu użytkownika. Aby dowiedzieć się, jak utworzyć solidną aplikację w jakości produkcyjnej, korzystając z działań w połączeniu z komponentami architektury, zapoznaj się z Przewodnikiem po architekturze aplikacji.

Pojęcia związane z cyklem aktywności

Klasa Activity zawiera podstawowy zestaw 6 wywołań zwrotnych, które pozwalają poruszać się między etapami cyklu życia aktywności: onCreate(), onStart(), onResume(), onPause(), onStop() i onDestroy(). System będzie wywoływał każde z tych wywołań zwrotnych w momencie, gdy aktywność wchodzi w nowy stan.

Rysunek 1 obrazuje ten model.

Rysunek 1. Uproszczona ilustracja cyklu życia aktywności.

Gdy użytkownik zacznie ją wyłączać, system wywołuje metody demontowania aktywności. W niektórych przypadkach aktywność zostaje tylko częściowo demontowana i wciąż znajduje się w pamięci, np. gdy użytkownik przełącza się na inną aplikację. W takich przypadkach może ona wrócić na pierwszy plan.

Jeśli użytkownik wraca do aktywności, wznawia się ona od miejsca, w którym została przerwana. Poza kilkoma wyjątkami aplikacje nie mogą uruchamiać działań, gdy działają w tle.

Prawdopodobieństwo zakończenia działania danego procesu przez system i jego wykonywanych w nim działań zależy od stanu tych działań. Więcej informacji o związku między stanem a luką w zabezpieczeniach związaną z wyrzucaniem znajdziesz w sekcji o stanie aktywności i wyrzucaniu z pamięci.

W zależności od złożoności działania prawdopodobnie nie musisz implementować wszystkich metod cyklu życia. Ważne jest jednak, by zrozumieć każdy z nich i wdrożyć te, dzięki którym aplikacja będzie działać zgodnie z oczekiwaniami użytkowników.

Wywołania zwrotne cyklu życia

W tej sekcji znajdziesz koncepcje i informacje o implementacji metod wywołań zwrotnych stosowanych w cyklu życia aktywności.

Niektóre działania należą do metod cyklu życia aktywności. Zamiast tego umieść kod implementujący działania zależnego komponentu w komponencie, a nie metodę cyklu życia aktywności. Aby to osiągnąć, musisz uwzględniać cykl życia zależnego komponentu. Aby dowiedzieć się, jak dostosować cykl życia komponentów zależnych do swojego cyklu życia, przeczytaj sekcję Obsługa cyklu życia przy użyciu komponentów uwzględniających cykl życia.

onCreate()

Musisz zaimplementować to wywołanie zwrotne, które jest uruchamiane, gdy system po raz pierwszy utworzy aktywność. Podczas tworzenia aktywności przechodzi ona w stan Utworzono. W metodzie onCreate() uruchom podstawowe logiki uruchamiania aplikacji, które odbywają się tylko raz w całym okresie działania.

Na przykład implementacja onCreate() może powiązać dane z listami, powiązać aktywność z tagiem ViewModel i utworzyć wystąpienie zmiennych zakresu klas. Ta metoda otrzymuje parametr savedInstanceState, który jest obiektem Bundle zawierającym wcześniej zapisany stan aktywności. Jeśli taka aktywność nigdy nie istniała, wartość obiektu Bundle jest wartością null.

Jeśli masz komponent identyfikujący cykl życia, który jest połączony z cyklem życia Twojej aktywności, otrzymuje on zdarzenie ON_CREATE. Metoda z adnotacją @OnLifecycleEvent jest wywoływana, aby komponent śledzący cykl życia mógł wykonać dowolny kod konfiguracji potrzebny do utworzenia stanu.

Poniższy przykład metody onCreate() przedstawia podstawową konfigurację działania, np. zadeklarowanie interfejsu (zdefiniowanego w pliku układu XML), zdefiniowanie zmiennych składowych i skonfigurowanie niektórych elementów interfejsu. W tym przykładzie plik układu XML przekazuje identyfikator zasobu pliku (R.layout.main_activity) do setContentView().

Kotlin

lateinit var textView: TextView

// Some transient state for the activity instance.
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState)

    // Recover the instance state.
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity)

    // Initialize member TextView so it is available later.
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState)
}

Java

TextView textView;

// Some transient state for the activity instance.
String gameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState);

    // Recover the instance state.
    if (savedInstanceState != null) {
        gameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity);

    // Initialize member TextView so it is available later.
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString(GAME_STATE_KEY, gameState);
    outState.putString(TEXT_VIEW_KEY, textView.getText());

    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState);
}

Zamiast definiować plik XML i przekazywać go do setContentView(), możesz utworzyć nowe obiekty View w kodzie aktywności i zbudować hierarchię widoków, wstawiając nowe obiekty View do ViewGroup. Następnie używaj tego układu, przekazując główny element ViewGroup do interfejsu setContentView(). Więcej informacji o tworzeniu interfejsu użytkownika znajdziesz w dokumentacji interfejsu.

Aktywność nie pozostaje w stanie Utworzona. Po zakończeniu wykonywania metody onCreate() działanie przechodzi w stan Started (Rozpoczęte), a system szybko wywołuje metody onStart() i onResume().

onStart()

Gdy aktywność przejdzie w stan rozpoczęty, system wywoła polecenie onStart(). To wywołanie powoduje, że aktywność jest widoczna dla użytkownika, gdy aplikacja przygotowuje się do wejścia na pierwszy plan i w ten sposób staje się interaktywna. W tej metodzie inicjowany jest np. kod utrzymujący interfejs użytkownika.

Gdy aktywność przejdzie do stanu Rozpoczęte, każdy komponent znający cykl życia powiązany z jego cyklem życia otrzyma zdarzenie ON_START.

Metoda onStart() szybko się kończy i tak jak w przypadku stanu utworzenia aktywność nie pozostaje w stanie rozpoczętym. Po zakończeniu wywołania zwrotnego działanie przechodzi w stan Wznów, a system wywołuje metodę onResume().

onWznów()

Gdy aktywność przejdzie w stan wznowienia, przejdzie na pierwszy plan, a system wywoła wywołanie zwrotne onResume(). Jest to stan, w którym aplikacja wchodzi w interakcję z użytkownikiem. Aplikacja pozostaje w tym stanie, dopóki nie zostanie ona odebrana, np. urządzenie odbiera połączenie, użytkownik przechodzi do innej czynności lub nie wyłączy się ekran urządzenia.

Gdy aktywność przejdzie w stan Wznowiono, każdy komponent wpływający na cykl życia powiązany z jego cyklem życia otrzyma zdarzenie ON_RESUME. W tym miejscu komponenty cyklu życia mogą włączyć wszystkie funkcje potrzebne do działania, gdy komponent jest widoczny i na pierwszym planie, np. uruchamianie podglądu kamery.

W momencie wystąpienia zdarzenia przerwania aktywność przechodzi w stan pause (wstrzymana), a system wywołuje wywołanie zwrotne onPause().

Jeśli działanie powróci ze stanu wstrzymania do stanu wznowienia, system ponownie wywoła metodę onResume(). Z tego powodu zaimplementuj onResume(), aby zainicjować komponenty publikowane w czasie onPause() i wykonać inne inicjalizacje, które muszą wystąpić za każdym razem, gdy aktywność przejdzie w stan wznowienia.

Oto przykład komponentu zależnego od cyklu życia, który uzyskuje dostęp do kamery, gdy komponent odbiera zdarzenie ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }
    ...
}

Java

public class CameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void initializeCamera() {
        if (camera == null) {
            getCamera();
        }
    }
    ...
}

Poprzedni kod inicjuje kamerę, gdy LifecycleObserver otrzyma zdarzenie ON_RESUME. Jednak w trybie wielu okien aktywność może być w pełni widoczna nawet wtedy, gdy jest wstrzymana. Jeśli na przykład aplikacja działa w trybie wielu okien, a użytkownik kliknie okno, które nie zawiera Twojej aktywności, aktywność zostanie wstrzymana.

Jeśli chcesz, aby kamera była aktywna tylko po wznowieniu aplikacji (widoczna i aktywna na pierwszym planie), zainicjuj ją po przedstawionym wcześniej zdarzeniu ON_RESUME. Jeśli chcesz, aby kamera pozostawała aktywna, gdy aktywność jest wstrzymana, ale widoczna, np. w trybie wielu okien, po wystąpieniu zdarzenia ON_START zainicjuj kamerę.

Jeśli jednak kamera jest aktywna w czasie wstrzymania aktywności, może zablokować jej dostęp do innej wznowionej aplikacji w trybie wielu okien. Czasami trzeba, aby kamera była aktywna, gdy aktywność jest wstrzymana, ale może to pogorszyć ogólne wrażenia użytkownika.

Dlatego dobrze się zastanów, w którym miejscu cyklu życia najlepiej przejmować kontrolę nad współdzielonymi zasobami systemowymi w trybie wielu okien. Więcej informacji o obsłudze trybu wielu okien znajdziesz w artykule Obsługa wielu okien.

Niezależnie od tego, w którym zdarzeniu kompilacji wybierzesz operację inicjowania, pamiętaj, aby zwolnić zasób za pomocą odpowiedniego zdarzenia cyklu życia. Jeśli zainicjujesz coś po zdarzeniu ON_START, zwolnij lub zakończ działanie po zdarzeniu ON_STOP. Jeśli zainicjujesz po zdarzeniu ON_RESUME, wersja po zdarzeniu ON_PAUSE.

Poprzedni fragment kodu umieszcza kod inicjowania kamery w komponencie identyfikującym cykl życia. Możesz zamiast tego umieścić ten kod bezpośrednio w wywołaniach zwrotnych cyklu życia aktywności, np. onStart() czy onStop(), ale nie zalecamy tego. Dodanie tej logiki do niezależnego komponentu uwzględniającego cykl życia umożliwia ponowne używanie go w wielu działaniach bez konieczności powielania kodu. Aby dowiedzieć się, jak utworzyć komponent wpływający na cykl życia, przeczytaj artykuł o obsłudze cyklu życia za pomocą komponentów odzwierciedlających cykl życia.

onPause()

System wywołuje tę metodę jako pierwszy sygnał, że użytkownik opuszcza aktywność, chociaż nie zawsze oznacza to, że aktywność jest niszczona. Wskazuje ona, że aktywność nie jest już na pierwszym planie, ale jest nadal widoczna, jeśli użytkownik korzysta z trybu wielu okien. Istnieje kilka powodów, dla których aktywność może mieć taki stan:

  • Zdarzenie, które przerywa wykonywanie aplikacji, jak opisano w sekcji dotyczącej wywołania zwrotnego onWznów(), wstrzymuje bieżące działanie. To najczęstszy przypadek.
  • W trybie wielu okien w danym momencie aktywna jest tylko jedna aplikacja, a system wstrzymuje wszystkie pozostałe.
  • Otwarcie nowej, półprzezroczystej aktywności, np. okna, wstrzymuje działanie, które obejmuje. Dopóki aktywność jest tylko częściowo widoczna, ale nie jest wyraźnie widoczna, zostaje wstrzymana.

Gdy aktywność jest wstrzymana, każdy komponent wpływający na cykl życia powiązany z jego cyklem życia otrzymuje zdarzenie ON_PAUSE. W tym przypadku komponenty cyklu życia mogą zatrzymać dowolną funkcję, która nie musi działać, gdy komponent nie jest na pierwszym planie. Może to być na przykład zatrzymanie podglądu kamery.

Użyj metody onPause(), aby wstrzymać lub dostosować operacje, które nie mogą być kontynuowane lub mogą być nadal moderowane, gdy element Activity jest w stanie wstrzymania, który powinien zostać wkrótce wznowiony.

Możesz też użyć metody onPause(), aby zwolnić zasoby systemowe, uchwyty dla czujników (np. GPS) lub dowolne zasoby, które wpływają na czas pracy baterii, gdy aktywność jest wstrzymana, a użytkownik ich nie potrzebuje.

Jednak jak wspomnieliśmy w sekcji na temat onResume(), wstrzymana aktywność może być nadal w pełni widoczna, jeśli aplikacja działa w trybie wielu okien. Rozważ użycie elementu onStop() zamiast onPause(), aby w pełni zwolnić lub dostosować zasoby i operacje związane z interfejsem, tak aby lepiej obsługiwały tryb wielu okien.

Ten przykład reakcji LifecycleObserver na zdarzenie ON_PAUSE jest odpowiednikiem poprzedniego przykładu zdarzenia ON_RESUME, który zwalnia kamerę, która uruchamia się po otrzymaniu zdarzenia ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }
    ...
}

Java

public class JavaCameraComponent implements LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }
    ...
}

W tym przykładzie umieszcza się kod zwalniający kamerę po odebraniu zdarzenia ON_PAUSE przez LifecycleObserver.

Wykonanie onPause() jest bardzo krótkie i niekoniecznie daje dużo czasu na wykonanie operacji zapisu. Dlatego nie używaj polecenia onPause() do zapisywania danych aplikacji i użytkownika, wykonywania wywołań sieciowych ani wykonywania transakcji w bazie danych. Działanie to może nie zostać ukończone przed zakończeniem pracy.

Intensywne operacje wyłączania w okresie onStop(). Więcej informacji o odpowiednich operacjach, które należy wykonać w czasie onStop(), znajdziesz w następnej sekcji. Więcej informacji o zapisywaniu danych znajdziesz w sekcji o zapisywaniu i przywracaniu stanu.

Zastosowanie metody onPause() nie oznacza, że aktywność przejdzie ze stanu wstrzymania. Aktywność pozostaje w tym stanie, dopóki nie zostanie wznowiona lub nie stanie się całkowicie niewidoczna dla użytkownika. Jeśli aktywność zostanie wznowiona, system ponownie wywoła wywołanie zwrotne onResume().

Jeśli aktywność wróci ze stanu wstrzymania do stanu wznowienia, system zachowa instancję Activity w pamięci i wywołuje ją, gdy system wywoła metodę onResume(). W tym scenariuszu nie musisz ponownie inicjować komponentów utworzonych w żadnej z metod wywołania zwrotnego, które doprowadziły do wznowienia. Jeśli aktywność staje się całkowicie niewidoczna, system wywołuje metodę onStop().

onStop()

Gdy aktywność nie jest już widoczna dla użytkownika, przechodzi w stan Zatrzymana i system wywołuje wywołanie zwrotne onStop(). Może się tak zdarzyć, gdy nowo uruchomiona aktywność obejmuje cały ekran. System wywołuje też onStop(), gdy aktywność dobiegnie końca.

Gdy aktywność przejdzie w stan Zatrzymana, każdy komponent znający cykl życia powiązany z jego cyklem życia otrzyma zdarzenie ON_STOP. W tym przypadku komponenty cyklu życia mogą zatrzymać dowolną funkcję, która nie musi działać, gdy komponent nie jest widoczny na ekranie.

W metodzie onStop() zwolnij lub dostosuj zasoby, które nie są potrzebne, gdy aplikacja nie jest widoczna dla użytkownika. Aplikacja może na przykład wstrzymywać animacje lub przełączać się ze szczegółowej na przybliżoną lokalizację. Użycie właściwości onStop() zamiast onPause() oznacza, że praca związana z interfejsem jest kontynuowana nawet wtedy, gdy użytkownik przegląda Twoją aktywność w trybie wielu okien.

Użyj też onStop(), aby przeprowadzić operacje wyłączania, które stosunkowo obciążają procesor. Jeśli na przykład nie możesz znaleźć lepszego czasu na zapisanie informacji w bazie danych, możesz to zrobić w czasie onStop(). Ten przykład przedstawia implementację onStop(), która zapisuje zawartość wersji roboczej w pamięci trwałej:

Kotlin

override fun onStop() {
    // Call the superclass method first.
    super.onStop()

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    val values = ContentValues().apply {
        put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
        put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
    }

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate(
            token,     // int token to correlate calls
            null,      // cookie, not used here
            uri,       // The URI for the note to update.
            values,    // The map of column names and new values to apply to them.
            null,      // No SELECT criteria are used.
            null       // No WHERE columns are used.
    )
}

Java

@Override
protected void onStop() {
    // Call the superclass method first.
    super.onStop();

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            uri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

Poprzedni przykładowy kod korzysta bezpośrednio z SQLite. Zalecamy jednak korzystanie z roomu – biblioteki trwałej, która zapewnia warstwę abstrakcji zamiast SQLite. Więcej informacji o zaletach korzystania z pomieszczeń i o tym, jak wdrożyć ją w aplikacji, znajdziesz w przewodniku Biblioteka trwałości sal.

Gdy aktywność przechodzi w stan Zatrzymana, obiekt Activity pozostaje w pamięci: zachowuje wszystkie informacje o stanie i członkostwie, ale nie jest podłączony do menedżera okien. Ta informacja jest przywracana w momencie wznowienia.

Nie musisz ponownie inicjować komponentów utworzonych w trakcie którejkolwiek metody wywołania zwrotnego prowadzącej do stanu wznowienia. System śledzi również bieżący stan każdego obiektu View w układzie, więc jeśli użytkownik wpisze tekst w widżecie EditText, treść zostanie zachowana, więc nie trzeba jej zapisywać ani przywracać.

Uwaga: po zatrzymaniu działania system może zniszczyć proces zawierający to działanie, jeśli będzie chciał odzyskać pamięć. Nawet jeśli system zniszczy proces po zatrzymaniu działania, zachowa on stan obiektów View, np. tekst w widżecie EditText, w Bundle – blobie parach klucz-wartość – i przywróci je, gdy użytkownik wróci do aktywności. Więcej informacji o przywracaniu aktywności, do której powraca użytkownik, znajdziesz w sekcji o zapisywaniu i przywracaniu stanu.

Ze stanu zatrzymana aktywność wraca do interakcji z użytkownikiem lub jest zakończona i znika. Jeśli aktywność przywróci, system wywoła onRestart(). Po zakończeniu uruchamiania Activity system wywołuje metodę onDestroy().

onDestroy()

Metoda onDestroy() jest wywoływana przed zniszczeniem aktywności. System wywołuje to wywołanie zwrotne z jednego z 2 powodów:

  1. Działanie dobiega końca, ponieważ użytkownik całkowicie zamknie je lub wywoła działanie finish().
  2. System tymczasowo niszczy tę aktywność z powodu zmiany konfiguracji, np. rotacji urządzenia lub przejścia w tryb wielu okien.

Gdy aktywność przejdzie do stanu zniszczenia, każdy komponent rozpoznający cykl życia powiązany z jego cyklem życia otrzyma zdarzenie ON_DESTROY. W tym momencie komponenty cyklu życia mogą wyczyścić potrzebne elementy, zanim Activity zostanie zniszczony.

Zamiast logiki w elemencie Activity ustalić, dlaczego jest niszczony, użyj obiektu ViewModel, aby przechowywać odpowiednie dane widoku dotyczące obiektu Activity. Jeśli w wyniku zmiany konfiguracji Activity zostanie odtworzony w wyniku zmiany konfiguracji, ViewModel nie musi nic robić, ponieważ zostaje zachowany i przekazany do kolejnej instancji Activity.

Jeśli Activity nie zostanie odtworzony, ViewModel ma wywoływaną metodę onCleared(), która może wyczyścić wszelkie dane potrzebne przed zniszczeniem. Te 2 scenariusze można odróżnić za pomocą metody isFinishing().

Jeśli aktywność dobiega końca, onDestroy() to ostatnie wywołanie zwrotne w cyklu życia, które otrzymuje aktywność. Jeśli w wyniku zmiany konfiguracji zostanie wywołana funkcja onDestroy(), system natychmiast utworzy nową instancję aktywności, a potem w nowej konfiguracji wywoła metodę onCreate().

Wywołanie zwrotne onDestroy() zwalnia wszystkie zasoby, które nie zostały zwolnione przez wcześniejsze wywołania zwrotne, na przykład onStop().

Stan aktywności i wyrzucenie z pamięci

System zamyka procesy, gdy trzeba zwolnić pamięć RAM. Prawdopodobieństwo zabicia danego procesu przez system zależy od jego stanu w danym momencie. Stan procesu zależy z kolei od stanu wykonywanych w nim działań. Tabela 1 przedstawia korelacje między stanem procesu, stanem działania i prawdopodobieństwem jego zakończenia przez system. Ta tabela ma zastosowanie tylko wtedy, gdy w procesie nie są uruchomione inne typy komponentów aplikacji.

Prawdopodobieństwo zabicia Stan procesu Stan ostatniej aktywności
Najniższe Pierwszy plan (już jest ostrość) Wznowiono
Niski poziom Widoczne (bez zaznaczenia) Rozpoczęcie/wstrzymanie
Wyższa Tło (niewidoczne) Zatrzymano
Najwyższy Brak Zniszczono

Tabela 1. Związek między cyklem życia procesu a stanem działania.

System nigdy nie kończy działania, aby zwolnić pamięć. Zamiast tego zatrzymuje on proces, w którym działa działanie, niszcząc nie tylko działanie, ale także wszystkie inne uruchomione w nim działania. Aby dowiedzieć się, jak zachować i przywrócić stan interfejsu użytkownika w przypadku wystąpienia śmierci procesu inicjowanego przez system, przeczytaj sekcję o zapisywaniu i przywracaniu stanu.

Użytkownik może też zakończyć proces, zamykając odpowiednią aplikację w Menedżerze aplikacji w Ustawieniach.

Więcej informacji o procesach znajdziesz w omówieniu procesów i wątków.

Zapisuję i przywracam przejściowy stan interfejsu użytkownika

Użytkownik oczekuje, że stan interfejsu aktywności pozostanie taki sam po zmianie konfiguracji, np. podczas rotacji lub przełączenia się w tryb wielu okien. Jednak po wprowadzeniu takiej zmiany konfiguracji system domyślnie niszczy aktywność, usuwając wszystkie stany interfejsu użytkownika zapisane w instancji aktywności.

Podobnie użytkownik oczekuje, że stan interfejsu użytkownika pozostanie taki sam, jeśli tymczasowo przejdzie z Twojej aplikacji na inną, a potem do niej wróci. System może jednak zniszczyć proces aplikacji podczas nieobecności użytkownika, w wyniku czego aktywność zostanie zatrzymana.

Gdy ograniczenia systemowe niszczą aktywność, zachowaj przejściowy stan interfejsu użytkownika za pomocą kombinacji elementów ViewModel, onSaveInstanceState() lub pamięci lokalnej. Więcej informacji na temat oczekiwań użytkowników w porównaniu z zachowaniem systemu oraz jak najlepiej zachować złożone dane stanu interfejsu użytkownika w przypadku aktywności inicjowanej przez system i śmierci procesów w artykule Zapisywanie stanów interfejsu użytkownika.

Z tej sekcji dowiesz się, czym jest stan instancji i jak wdrożyć metodę onSaveInstance(), która jest wywołaniem zwrotnym samej aktywności. Jeśli dane interfejsu użytkownika są ograniczone, możesz użyć samego parametru onSaveInstance(), aby zachować stan interfejsu zarówno po zmianach w konfiguracji, jak i po zakończeniu procesu zainicjowanego przez system. Ponieważ jednak onSaveInstance() wiąże się z kosztami serializacji/deserializacji, w większości przypadków używasz zarówno ViewModel, jak i onSaveInstance(), jak opisano w sekcji Zapisywanie stanów interfejsu użytkownika.

Uwaga: więcej informacji o zmianach w konfiguracji, ograniczaniu w razie potrzeby aktywności oraz o tym, jak reagować na te zmiany konfiguracji z poziomu widoku systemu i Jetpack Compose, znajdziesz na stronie Obsługa zmian konfiguracji.

Stan instancji

W kilku sytuacjach aktywność jest niszczona w wyniku normalnego działania aplikacji, np. gdy użytkownik kliknie przycisk Wstecz lub aktywność sygnalizuje własne zniszczenie, wywołując metodę finish().

Gdy aktywność zostanie skasowana, ponieważ użytkownik naciśnie Wstecz lub działanie się zakończy, koncepcja tej instancji Activity określona przez system i użytkownik zostanie skasowana na zawsze. W tych sytuacjach oczekiwania użytkownika są zgodne z zachowaniem systemu, a Ty nie musisz nic robić.

Jeśli jednak system zniszczy aktywność z powodu ograniczeń systemu (takich jak zmiana konfiguracji lub użycie pamięci), mimo że instancja Activity już nie istnieje, system pamięta, że istniała. Jeśli użytkownik spróbuje wrócić do aktywności, system utworzy nową instancję aktywności przy użyciu zbioru zapisanych danych opisujących stan aktywności w momencie jej zniszczenia.

Zapisane dane używane przez system do przywracania poprzedniego stanu to stan instancji. To zbiór par klucz-wartość przechowywanych w obiekcie Bundle. Domyślnie system używa stanu instancji Bundle do zapisywania informacji o każdym obiekcie View w układzie aktywności, np. wartości tekstowej wpisanej w widżecie EditText.

Jeśli więc instancja aktywności zostanie zniszczona i odtworzona, stan układu zostanie przywrócony do poprzedniego stanu bez konieczności użycia kodu. Może ona jednak zawierać więcej informacji o stanie, które chcesz przywrócić, na przykład zmienne członkostwa, które śledzą postępy użytkownika.

Uwaga: aby system Android przywrócił stan widoków danych w Twojej aktywności, każdy widok musi mieć unikalny identyfikator podany przez atrybut android:id.

Obiekt Bundle nie jest odpowiedni do przechowywania więcej niż trywialna ilość danych, ponieważ wymaga serializacji w wątku głównym i wykorzystuje pamięć procesu systemowego. Aby zachować więcej niż bardzo małą ilość danych, połącz metody ich zachowania, korzystając z trwałej pamięci lokalnej, metody onSaveInstanceState() i klasy ViewModel zgodnie z opisem w sekcji Zapisywanie stanów interfejsu.

Zapisz prosty, nieskomplikowany stan interfejsu za pomocą funkcji onSaveInstanceState()

Gdy aktywność zaczyna się zatrzymywać, system wywołuje metodę onSaveInstanceState(), aby aktywność mogła zapisywać informacje o stanie w grupie stanów instancji. Domyślna implementacja tej metody zapisuje przejściowe informacje o stanie hierarchii widoków aktywności, takie jak tekst w widżecie EditText lub pozycja przewijania ListView.

Aby zapisać dodatkowe informacje o stanie instancji dla swojej aktywności, zastąp onSaveInstanceState() i dodaj pary klucz-wartość do obiektu Bundle, który jest zapisywany w przypadku nieoczekiwanego zniszczenia aktywności. Gdy zastępujesz onSaveInstanceState(), musisz wywołać implementację klasy nadrzędnej, jeśli chcesz, aby domyślna implementacja zapisywała stan hierarchii widoków. Widać to w tym przykładzie:

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state.
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

Java

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...


@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state.
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    savedInstanceState.putInt(STATE_LEVEL, currentLevel);

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(savedInstanceState);
}

Uwaga: parametr onSaveInstanceState() nie jest wywoływany, gdy użytkownik jawnie zamknie działanie lub w innych przypadkach po wywołaniu metody finish().

Aby zapisać dane trwałe, takie jak preferencje użytkownika lub dane w bazie danych, wykorzystaj odpowiednie możliwości, gdy Twoja aktywność działa na pierwszym planie. Jeśli nie pojawi się taka możliwość, zapisz trwałe dane podczas metody onStop().

Przywróć stan interfejsu aktywności przy użyciu zapisanego stanu instancji

Gdy aktywność zostanie odtworzona po jej wcześniejszym zniszczeniu, możesz przywrócić zapisany stan instancji z Bundle, który system przekazuje do aktywności. Obie metody wywołania zwrotnego onCreate() i onRestoreInstanceState() otrzymują tę samą wartość Bundle, która zawiera informacje o stanie instancji.

Ponieważ metoda onCreate() jest wywoływana, czy system tworzy nową instancję aktywności, czy odtwarza poprzednią, przed próbą odczytania musisz sprawdzić, czy stan Bundle ma wartość null. W przypadku wartości null system tworzy nową instancję działania, a nie przywraca wcześniej zniszczoną instancję.

Ten fragment kodu pokazuje, jak w onCreate() można przywrócić niektóre dane stanu:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state.
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        // Restore value of members from saved state.
        currentScore = savedInstanceState.getInt(STATE_SCORE);
        currentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Zamiast przywrócić stan podczas onCreate(), możesz wdrożyć parametr onRestoreInstanceState(), który jest wywoływany przez system po metodzie onStart(). System wywołuje funkcję onRestoreInstanceState() tylko wtedy, gdy istnieje zapisany stan do przywrócenia, więc nie musisz sprawdzać, czy Bundle ma wartość null.

Kotlin

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance.
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

Java

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance.
    currentScore = savedInstanceState.getInt(STATE_SCORE);
    currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

Uwaga: zawsze wywołuj implementację klasy nadrzędnej onRestoreInstanceState(), by implementacja domyślna mogła przywrócić stan hierarchii widoków.

Przechodzenie między działaniami

Aplikacja prawdopodobnie wiele razy wejdzie w interakcję, a następnie ją zakończy, np. gdy kliknie na urządzeniu przycisk Wstecz lub wywoła inne działanie.

W tej sekcji znajdziesz tematy, z którymi musisz się zapoznać, aby skutecznie przenieść działania. Dotyczą one rozpoczynania działania od innej aktywności, zapisywania jej stanu i przywracania stanu aktywności.

Rozpoczynanie jednej aktywności od drugiej

Aktywność często wymaga w pewnym momencie rozpoczęcia innego działania. Taka potrzeba zachodzi np. wtedy, gdy aplikacja musi przejść z bieżącego ekranu na nowy.

W zależności od tego, czy dana aktywność wymaga uzyskania wyników z nowego działania, które ma się wkrótce rozpocząć, możesz rozpocząć nowe działanie za pomocą metody startActivity() lub startActivityForResult(). W obu przypadkach przekazujesz obiekt Intent.

Obiekt Intent określa dokładnie aktywność, którą chcesz rozpocząć, lub opisuje typ działania, które chcesz wykonać. System sam wybiera odpowiednie działanie, nawet pochodzące z innej aplikacji. Obiekt Intent może też zawierać niewielkie ilości danych na potrzeby uruchomionego działania. Więcej informacji o klasie Intent znajdziesz w artykule o intencjach i filtrach intencji.

startActivity()

Jeśli nowo rozpoczęte działanie nie musi zwracać wyniku, bieżące działanie może je uruchomić, wywołując metodę startActivity().

Pracując we własnej aplikacji, często musisz po prostu uruchomić znaną aktywność. Na przykład ten fragment kodu pokazuje, jak uruchomić działanie o nazwie SignInActivity.

Kotlin

val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)

Java

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

Aplikacja może też używać danych z Twojej aktywności do wykonywania pewnych działań, np. wysyłania e-maili lub SMS-ów albo aktualizacji stanu. W takim przypadku aplikacja może nie mieć własnych działań do wykonywania takich działań, więc zamiast tego możesz użyć działań dostarczonych przez inne aplikacje na urządzeniu, które mogą je wykonać za Ciebie.

W tym przypadku bardzo ważne są intencje. Możesz utworzyć intencję opisującą działanie, które chcesz wykonać, a system uruchomi tę czynność z innej aplikacji. Jeśli istnieje wiele działań, które mogą obsłużyć intencję, użytkownik może wybrać, której użyć. Jeśli na przykład chcesz zezwolić użytkownikowi na wysyłanie e-maili, możesz utworzyć taką intencję:

Kotlin

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

Java

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

Dodatkowy EXTRA_EMAIL dodany do intencji to tablica ciągów adresów e-mail, na które należy wysłać e-maila. Gdy aplikacja do obsługi poczty e-mail odpowiada na tę intencję, odczytuje tablicę ciągów znaków podaną w dodatkowym elemencie i umieszcza adresy w polu „do” formularza tworzenia e-maila. W takiej sytuacji działanie aplikacji e-mailowej rozpoczyna się, a gdy użytkownik kończy, działanie jest wznawiane.

startActivityForResult()

Czasami chcesz otrzymać wynik aktywności, która się zakończy. Możesz na przykład uruchomić działanie, które pozwala użytkownikowi wybrać osobę z listy kontaktów. Po jego zakończeniu zwraca wybraną osobę. W tym celu wywołujesz metodę startActivityForResult(Intent, int), która jako liczba całkowita identyfikuje wywołanie.

Ten identyfikator służy do odróżniania wielu wywołań funkcji startActivityForResult(Intent, int) od tej samej aktywności. Nie jest to identyfikator globalny i nie jest narażony na konflikt z innymi aplikacjami czy działaniami. Wynik jest uzyskiwany za pomocą metody onActivityResult(int, int, Intent).

Po zakończeniu działania aktywności podrzędnej może ona wywołać funkcję setResult(int), aby zwrócić dane do jednostki nadrzędnej. Aktywność podrzędna musi dostarczać kod wyniku, którym mogą być standardowe wyniki (RESULT_CANCELED, RESULT_OK) lub dowolne wartości niestandardowe zaczynające się od RESULT_FIRST_USER.

Oprócz tego aktywność podrzędna może opcjonalnie zwracać obiekt Intent zawierający dodatkowe dane, których potrzebuje. Do otrzymania informacji działanie nadrzędne wykorzystuje metodę onActivityResult(int, int, Intent) w połączeniu z identyfikatorem liczby całkowitej podanej pierwotnie przez działanie nadrzędne.

Jeśli aktywność podrzędna z jakiegokolwiek powodu nie powiedzie się, na przykład ulegnie awarii, aktywność nadrzędna otrzyma wynik z kodem RESULT_CANCELED.

Kotlin

class MyActivity : Activity() {
    // ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    // A contact was picked. Display it to the user.
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }

    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Java

public class MyActivity extends Activity {
     // ...

     static final int PICK_CONTACT_REQUEST = 0;

     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
             // When the user center presses, let them pick a contact.
             startActivityForResult(
                 new Intent(Intent.ACTION_PICK,
                 new Uri("content://contacts")),
                 PICK_CONTACT_REQUEST);
            return true;
         }
         return false;
     }

     protected void onActivityResult(int requestCode, int resultCode,
             Intent data) {
         if (requestCode == PICK_CONTACT_REQUEST) {
             if (resultCode == RESULT_OK) {
                 // A contact was picked. Display it to the user.
                 startActivity(new Intent(Intent.ACTION_VIEW, data));
             }
         }
     }
 }

Koordynowanie działań

Gdy jedna aktywność rozpoczyna inne, oboje doświadczają przejścia w cyklu życia. Pierwsza aktywność przestanie działać i wejdzie w stan wstrzymania lub zatrzymanego, a druga zostanie utworzona. Jeśli działania te udostępniają dane zapisane na dysku lub w innym miejscu, pamiętaj, że pierwsza aktywność nie zostaje całkowicie zatrzymana przed utworzeniem drugiej. Rozpoczęcie drugiego etapu nakłada się raczej na proces zatrzymywania pierwszej.

Kolejność wywołań zwrotnych cyklu życia jest dobrze zdefiniowana, zwłaszcza gdy 2 działania należą do tego samego procesu – czyli z tej samej aplikacji – i pochodzą między drugim. Oto kolejność operacji, które będą wykonywane, gdy działanie A rozpoczyna działanie B:

  1. Jest wykonywana metoda onPause() działania A.
  2. Metody onCreate(), onStart() i onResume() działania B są wykonywane w kolejności. Aktywność B jest teraz skupiona na użytkownikach.
  3. Jeśli aktywność A nie jest już widoczna na ekranie, wykonywana jest jej metoda onStop().

Ta sekwencja wywołań zwrotnych cyklu życia pozwala zarządzać przenoszeniem informacji z jednego działania do drugiego.