Jak korzystać z cyklu życia i stanu aktywności

1. Witamy

Ten praktyczny moduł jest częścią modułu 1: Wprowadzenie do kursu Podstawy programowania na Androida (wersja 2). Najwięcej korzyści z tego kursu odniesiesz, jeśli będziesz wykonywać laboratoria programistyczne w kolejności:

  • Pełną listę laboratoriów programistycznych w ramach kursu znajdziesz w artykule Codelabs dla programistów Androida (wersja 2).
  • Szczegółowe informacje o kursie, w tym linki do wszystkich rozdziałów z koncepcjami, aplikacji i slajdów, znajdziesz w podręczniku Podstawy programowania na Androida (wersja 2).

Wprowadzenie

W tym praktycznym samouczku dowiesz się więcej o cyklu życia aktywności. Cykl życia to zbiór stanów, w których może znajdować się aktywność w całym okresie jej istnienia, od momentu utworzenia do momentu jej usunięcia i odzyskania przez system jej zasobów. Gdy użytkownik przełącza się między aktywnościami w aplikacji (a także wchodzi do niej i z niej wychodzi), aktywności przechodzą przez różne stany w swoim cyklu życia.

IDouble trouble

Każdy etap cyklu aktywności ma odpowiednią metodę wywołania: onCreate(), onStart(), onPause() itd. Gdy aktywność zmieni stan, zostanie wywołana powiązana metoda wywołania zwrotnego. Jedną z tych metod, które już znasz, jest onCreate(). Zastępując dowolną metodę wywołania cyklu życia w klasach Activity, możesz zmienić domyślne zachowanie aktywności w reakcji na działania użytkownika lub systemu.

Stan aktywności może się też zmieniać w odpowiedzi na zmiany konfiguracji urządzenia, np. gdy użytkownik obróci urządzenie z poziomego na pionowe. Gdy nastąpią te zmiany konfiguracji, aktywność zostanie zniszczona i powtórnie utworzona w stanie domyślnym, a użytkownik może utracić informacje wprowadzone w ramach tej aktywności. Aby uniknąć dezorientacji użytkowników, zadbaj o to, aby aplikacja zapobiegała nieoczekiwanemu utracie danych. W dalszej części tego praktycznego kursu eksperymentujesz z zmianami konfiguracji i dowiesz się, jak zachować stan aktywności w odpowiedzi na zmiany konfiguracji urządzenia i inne zdarzenia cyklu życia aktywności.

W tej części praktycznej dodasz do aplikacji TwoActivities instrukcje logowania i obserwujesz zmiany w cyklu życia aktywności podczas korzystania z aplikacji. Następnie zaczniesz pracować z tymi zmianami i poznasz sposób obsługi danych wejściowych użytkownika w tych warunkach.

Wymagania wstępne

Powinieneś mieć możliwość:

  • Utwórz i uruchom projekt aplikacji w Android Studio.
  • dodawać do aplikacji instrukcje logowania i wyświetlać te logi w panelu Logcat;
  • zrozumieć działanie aktywności i intencji oraz umieć z nimi pracować,

Czego się nauczysz

  • Jak działa cykl życia działania
  • Gdy aktywność rozpoczyna się, jest wstrzymywana, kończy się i zostaje zniszczona.
  • Informacje o metodach wywołania powiązanych z cyklem życia i zmianami w działaniach.
  • Efekt działań (np. zmian konfiguracji), które mogą powodować zdarzenia cyklu życia aktywności.
  • Jak zachować stan działania w zdarzeniu cyklu życia.

Jakie zadania wykonasz

  • Dodaj do aplikacji TwoActivities kod z poprzedniego praktycznego ćwiczenia, aby zaimplementować różne wywołania zwrotne cyklu działania aktywności, które obejmują instrukcje rejestrowania.
  • Obserwuj zmiany stanu podczas działania aplikacji i interakcji z każdą aktywnością w aplikacji.
  • Zmodyfikuj aplikację, aby zachować stan instancji aktywności, która została nieoczekiwanie utworzona w odpowiedzi na zachowanie użytkownika lub zmianę konfiguracji na urządzeniu.

2. Aplikacje ogółem

W tym praktycznym ćwiczeniu dodasz do aplikacji TwoActivities. Wygląd i działanie aplikacji są mniej więcej takie same jak w poprzednim ćwiczeniu. Zawiera 2 implementacje aktywności i umożliwia użytkownikowi wysyłanie danych między nimi. Zmiany wprowadzone w aplikacji w ramach tego ćwiczenia nie wpłyną na widoczne zachowanie użytkowników.

3. 3. Zadanie 1. Dodaj wywołania zwrotne cyklu życia do klasy TwoActivities

W tym zadaniu zaimplementujesz wszystkie metody wywołania cyklu życia aktywności, aby wyprowadzać komunikaty do logcat podczas wywoływania tych metod. Te komunikaty dziennika pozwolą Ci zobaczyć, kiedy cykl życia aktywności zmienia stan, i jak te zmiany stanu wpływają na działanie aplikacji.

1.1 (Opcjonalnie) Skopiuj projekt TwoActivities

W zadaniach praktycznych w tym module zmodyfikujesz istniejący projekt TwoActivities utworzony w poprzednim module praktycznym. Jeśli wolisz zachować poprzedni projekt TwoActivities, wykonaj czynności opisane w sekcji Dodatek: narzędzia, aby utworzyć kopię projektu.

1.2 Wdrażanie wywołań zwrotnych w MainActivity

  1. Otwórz projekt TwoActivities w Android Studio i w panelu Projekt > Android otwórz MainActivity.
  2. W metodzie onCreate() dodaj te instrukcje logowania:
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
  1. Dodaj zastąpienie dla wywołania zwrotnego onStart() z oświadczeniem w dzienniku dla tego zdarzenia:
@Override
public void onStart(){
    super.onStart();
    Log.d(LOG_TAG, "onStart");
}

Aby użyć skrótu, w Android Studio kliknij Kod > Zastąpij metody. Pojawi się okno z wszystkimi metodami, które możesz zastąpić w swoich zajęciach. Wybranie co najmniej 1 metody wywołania zwrotnego z listy spowoduje wstawienie pełnego szablonu dla tych metod, w tym wymaganego wywołania superklasy.

  1. Użyj metody onStart() jako szablonu do implementacji wywołań zwrotnych cyklu życia onPause(), onRestart(), onResume(), onStop() i onDestroy().

Wszystkie metody wywołania mają te same sygnatury (z wyjątkiem nazwy). Jeśli skopiujesz i wkleisz metodę onStart(), aby utworzyć inne metody wywołania, pamiętaj, aby zaktualizować zawartość, aby wywołać właściwą metodę w superklasie, i zapisać właściwą metodę.

  1. Uruchom aplikację.
  2. Kliknij kartę Logcat u dołu Android Studio, aby wyświetlić panel Logcat. Powinny pojawić się 3 wiadomości dziennika, które pokazują 3 stany cyklu życia, przez które przeszła aktywność po rozpoczęciu:
D/MainActivity: -------
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: onResume

1.3 Wdróż wywołania zwrotne cyklu życia w klasie SecondActivity

Teraz, gdy zaimplementujesz metody wywołania cyklu życia w MainActivity, zrób to samo w przypadku SecondActivity.

  1. Otwórz SecondActivity.
  2. U góry klasy dodaj stałą dla zmiennej LOG_TAG:
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
  1. Dodaj do drugiej aktywności wywołania zwrotne cyklu życia i polecenia logowania. (metody wywołania możesz skopiować z MainActivity).
  2. Dodaj instrukcję logowania do metody returnReply() tuż przed metodą finish():
Log.d(LOG_TAG, "End SecondActivity");

1.4 Obserwuj log podczas działania aplikacji**

  1. Uruchom aplikację.
  2. Kliknij kartę Logcat u dołu Android Studio, aby wyświetlić panel Logcat.
  3. Wpisz Aktywność w polu wyszukiwania. Dziennik logcat na Androidzie może być bardzo długi i nieuporządkowany. Ponieważ zmienna LOG_TAG w każdej klasie zawiera albo słowo MainActivity, albo SecondActivity, to słowo kluczowe pozwala filtrować log tylko pod kątem interesujących Cię elementów.

IDouble trouble

Eksperymentuj z aplikacją i zauważ, że zdarzenia cyklu życia występują w odpowiedzi na różne działania. Spróbuj wykonać te czynności:

  • Używaj aplikacji normalnie (wysyłaj wiadomości, odpowiadaj na wiadomości).
  • Aby wrócić z drugiej czynności do czynności głównej, użyj przycisku Wstecz.
  • Aby wrócić z drugiej aktywności do głównej, kliknij strzałkę w górę na pasku aplikacji.
  • Obróć urządzenie w różnych momentach w głównej i drugiej aktywności w aplikacji i obserwuj, co dzieje się w dzienniku i na ekranie.
  • Naciśnij przycisk Przegląd (kwadratowy przycisk po prawej stronie przycisku Strona główna) i zamknij aplikację (kliknij X).
  • Wróć do ekranu głównego i uruchom ponownie aplikację.

WSKAZÓWKA: jeśli aplikacja działa w emulatorze, możesz symulować obrót za pomocą klawiszy Ctrl + F11 lub Ctrl + Fn + F11.

Kod rozwiązania zadania 1

Poniższe fragmenty kodu pokazują kod rozwiązania pierwszego zadania.

MainActivity

Poniższe fragmenty kodu pokazują dodany kod w MainActivity, ale nie całą klasę.

Metoda onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Log the start of the onCreate() method.
        Log.d(LOG_TAG, "-------");
        Log.d(LOG_TAG, "onCreate");

        // Initialize all the view variables.
        mMessageEditText = findViewById(R.id.editText_main);
        mReplyHeadTextView = findViewById(R.id.text_header_reply);
        mReplyTextView = findViewById(R.id.text_message_reply);
}

Inne metody cyklu życia:

@Override
protected void onStart() {
        super.onStart();
        Log.d(LOG_TAG, "onStart");
}

@Override
protected void onPause() {
        super.onPause();
        Log.d(LOG_TAG, "onPause");
}

@Override
protected void onRestart() {
        super.onRestart();
        Log.d(LOG_TAG, "onRestart");
}

@Override
protected void onResume() {
        super.onResume();
        Log.d(LOG_TAG, "onResume");
}

@Override
protected void onStop() {
        super.onStop();
        Log.d(LOG_TAG, "onStop");
}

@Override
protected void onDestroy() {
        super.onDestroy();
        Log.d(LOG_TAG, "onDestroy");
}

SecondActivity

Poniższe fragmenty kodu pokazują dodany kod w klasie SecondActivity, ale nie całą klasę.

U góry klasy SecondActivity:

private static final String LOG_TAG = SecondActivity.class.getSimpleName();

Metoda returnReply():

public void returnReply(View view) {
        String reply = mReply.getText().toString();
        Intent replyIntent = new Intent();
        replyIntent.putExtra(EXTRA_REPLY, reply);
        setResult(RESULT_OK, replyIntent);
        Log.d(LOG_TAG, "End SecondActivity");
        finish();
}

Inne metody cyklu życia:

To samo co w MainActivity powyżej.

4. 4. Zadanie 2. Zapisywanie i przywracanie stanu instancji aktywności

W zależności od zasobów systemowych i zachowania użytkowników poszczególne czynności w aplikacji mogą być niszczone i odtwarzane częściej, niż Ci się wydaje.

Możesz zauważyć to zachowanie w ostatniej sekcji, gdy obrócisz urządzenie lub jego emulator. Obrócenie urządzenia to jeden z przykładów zmiany konfiguracji urządzenia. Chociaż rotacja jest najczęstszą, wszystkie zmiany konfiguracji powodują zniszczenie bieżącej aktywności i jej ponowne utworzenie, jakby była nowa. Jeśli nie uwzględnisz tego zachowania w kodzie, po zmianie konfiguracji układ aktywności może powrócić do domyślnego wyglądu i wartości początkowych, a użytkownicy mogą utracić swoje miejsce, dane lub stan postępów w aplikacji.

Stan każdej aktywności jest przechowywany jako zestaw par klucz-wartość w obiekcie pakietu o nazwie stan instancji aktywności. System zapisuje informacje o domyślnym stanie w pakiecie stanu instancji tuż przed zatrzymaniem aktywności i przekazuje ten pakiet do nowej instancji aktywności w celu przywrócenia.

Aby uniknąć utraty danych w komponencie Activity, gdy zostanie on nieoczekiwanie zniszczony i powtórnie utworzony, musisz zaimplementować metodę onSaveInstanceState(). System wywołuje tę metodę w Twojej aktywności (między onPause() a onStop()), gdy istnieje możliwość jej zniszczenia i ponownego utworzenia.

Dane zapisane w stanie instancji są specyficzne tylko dla tej instancji danej aktywności w bieżącej sesji aplikacji. Gdy zatrzymasz i ponownie uruchomisz nową sesję aplikacji, stan instancji aktywności zostanie utracony, a wygląd aktywności wróci do domyślnego. Jeśli chcesz zapisywać dane użytkowników między sesjami aplikacji, użyj udostępnionych preferencji lub bazy danych. Więcej informacji na ten temat znajdziesz w jednym z następnych praktycznych ćwiczeń.

2.1 Zapisz stan instancji Activity za pomocą onSaveInstanceState()

Zauważysz pewnie, że obrócenie urządzenia w żaden sposób nie wpływa na stan drugiej czynności. Dzieje się tak, ponieważ drugi układ i stan działania są generowane na podstawie układu i intencji, które je aktywowały. Nawet jeśli aktywność zostanie ponownie utworzona, intencja nadal będzie dostępna i dane w niej zawarte będą nadal używane za każdym razem, gdy wywoływana jest metoda onCreate() w drugiej aktywności.

Możesz też zauważyć, że w każdej aktywności tekst wpisany w elementach EditText wiadomości lub odpowiedzi jest zachowywany nawet po obróceniu urządzenia. Dzieje się tak, ponieważ informacje o stanie niektórych elementów widoku w układzie są automatycznie zapisywane po zmianach konfiguracji, a bieżąca wartość elementu EditText jest jednym z takich przypadków.

Jedynymi elementami stanu aktywności, które Cię interesują, są elementy TextView nagłówka odpowiedzi i tekst odpowiedzi w głównej aktywności. Oba elementy TextView są domyślnie niewidoczne; pojawiają się dopiero po wysłaniu wiadomości z drugiej aktywności do głównej aktywności.

W tym zadaniu dodasz kod, który zachowa stan instancji tych 2 elementów TextView za pomocą metody onSaveInstanceState().

  1. Otwórz MainActivity.
  2. Dodaj do aktywności tę podstawową implementację metody onSaveInstanceState() lub użyj opcji Kod > Zastąp metody, aby wstawić podstawową zastępczą implementację.
@Override
public void onSaveInstanceState(Bundle outState) {
          super.onSaveInstanceState(outState);
}
  1. Sprawdź, czy nagłówek jest obecnie widoczny, a jeśli tak, zapisz stan widoczności w bundle stanu za pomocą metody putBoolean() i klucza „reply_visible”.
 if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
        outState.putBoolean("reply_visible", true);
    }

Pamiętaj, że nagłówek i tekst odpowiedzi są niewidoczne, dopóki nie otrzymasz odpowiedzi z drugiej aktywności. Jeśli nagłówek jest widoczny, oznacza to, że są dane odpowiedzi, które należy zapisać. Pamiętaj, że interesuje nas tylko stan widoczności – nie musisz zapisywać rzeczywistego tekstu nagłówka, ponieważ nigdy się nie zmienia.

  1. W tym samym teście dodaj tekst odpowiedzi do pakietu.
outState.putString("reply_text",mReplyTextView.getText().toString());

Jeśli nagłówek jest widoczny, możesz założyć, że sama wiadomość z odpowiedzią też jest widoczna. Nie musisz testować ani zapisywać bieżącego stanu widoczności wiadomości z odpowiedzią. Do pakietu stanu trafia tylko tekst wiadomości z kluczem „reply_text”.

Stan zapisywany jest tylko dla tych elementów widoku, które mogą ulec zmianie po utworzeniu aktywności. Inne elementy widoku w aplikacji (EditText, Button) można w każdej chwili odtworzyć na podstawie układu domyślnego.

Pamiętaj, że system zapisuje stan niektórych elementów widoku, takich jak zawartość pola tekstowego EditText.

2.2 Przywróć stan instancji aktywności w metodzie onCreate()

Po zapisaniu stanu instancji aktywności musisz go też przywrócić, gdy zostanie ona ponownie utworzona. Możesz to zrobić w metodzie onCreate() lub za pomocą wywołania zwrotnego onRestoreInstanceState(), który jest wywoływany po metodzie onStart() po utworzeniu aktywności.

W większości przypadków lepszym miejscem do przywracania stanu aktywności jest metoda onCreate(), która zapewnia, że interfejs użytkownika, w tym stan, jest dostępny jak najszybciej. Czasami wygodniej jest to zrobić w metodzie onRestoreInstanceState() po zakończeniu inicjalizacji lub umożliwić podklasom podjęcie decyzji o tym, czy mają użyć domyślnej implementacji.

  1. W metodzie onCreate() po zainicjowaniu zmiennych View za pomocą findViewById() dodaj test, aby upewnić się, że savedInstanceState nie jest null.
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);

// Restore the state.
if (savedInstanceState != null) {
}

Gdy Aktywność zostanie utworzona, system przekaże do onCreate() jako jedyny argument stan Bundle. Gdy po raz pierwszy wywołasz metodę onCreate() i uruchamiasz aplikację, Bundle jest nullem – przy pierwszym uruchomieniu aplikacji nie ma jeszcze żadnego stanu. Kolejne wywołania metody onCreate() mają pakiet wypełniony danymi zapisanymi w metodie onSaveInstanceState().

  1. W ramach tej weryfikacji pobierz bieżącą widoczność (prawda lub fałsz) z elementu Bundle za pomocą klucza „reply_visible”.
if (savedInstanceState != null) {
    boolean isVisible = 
                     savedInstanceState.getBoolean("reply_visible");
}
  1. Poniżej tego wiersza dodaj test dla zmiennej isVisible.
if (isVisible) {
}

Jeśli w pakiecie stanów jest klucz reply_visible (a wartość isVisible jest więc równa true), musisz przywrócić stan.

  1. W teście isVisible ustaw nagłówek jako widoczny.
mReplyHeadTextView.setVisibility(View.VISIBLE);
  1. Pobierz wiadomość tekstową z pakietu za pomocą klucza „reply_text” i ustaw odpowiedź TextView, aby wyświetlić ten ciąg znaków.
mReplyTextView.setText(savedInstanceState.getString("reply_text"));
  1. Upewnij się, że widoczny jest też element TextView odpowiedzi:
mReplyTextView.setVisibility(View.VISIBLE);
  1. Uruchom aplikację. Spróbuj obrócić urządzenie lub emulator, aby sprawdzić, czy wiadomość z odpowiedzią (jeśli taka istnieje) pozostaje na ekranie po ponownym utworzeniu aktywności.

Kod rozwiązania zadania 2

Poniższe fragmenty kodu pokazują rozwiązanie tego zadania.

MainActivity

Poniższe fragmenty kodu pokazują dodany kod w MainActivity, ale nie całą klasę.

Metoda onSaveInstanceState():

@Override
public void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);
   // If the heading is visible, message needs to be saved.
   // Otherwise we're still using default layout.
   if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
       outState.putBoolean("reply_visible", true);
       outState.putString("reply_text", 
                      mReplyTextView.getText().toString());
   }
}

Metoda onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   Log.d(LOG_TAG, "-------");
   Log.d(LOG_TAG, "onCreate");

   // Initialize all the view variables.
   mMessageEditText = findViewById(R.id.editText_main);
   mReplyHeadTextView = findViewById(R.id.text_header_reply);
   mReplyTextView = findViewById(R.id.text_message_reply);

   // Restore the saved state. 
   // See onSaveInstanceState() for what gets saved.
   if (savedInstanceState != null) {
       boolean isVisible = 
                     savedInstanceState.getBoolean("reply_visible");
       // Show both the header and the message views. If isVisible is
       // false or missing from the bundle, use the default layout.
       if (isVisible) {
           mReplyHeadTextView.setVisibility(View.VISIBLE);
           mReplyTextView.setText(savedInstanceState
                                  .getString("reply_text"));
           mReplyTextView.setVisibility(View.VISIBLE);
       }
   }
}

Ukończony projekt:

Projekt w Android Studio: TwoActivitiesLifecycle

5. Kodowanie

Wyzwanie: utwórz prostą aplikację do tworzenia listy zakupów z główną aktywnością dla listy tworzonej przez użytkownika i drugą aktywnością dla listy typowych produktów.

  • Główna aktywność powinna zawierać listę do utworzenia, która powinna składać się z 10 pustych elementów TextView.
  • Przycisk „Dodaj produkt” w głównej czynności uruchamia drugą czynność, która zawiera listę typowych produktów (ser, ryż, jabłka itp.). Do wyświetlania elementów użyj elementów przycisku.
  • Wybranie elementu powoduje powrót użytkownika do głównej czynności i zaktualizowanie pustego elementu TextView, aby uwzględnić wybrany element.

Użyj intencji, aby przekazać informacje z jednego Activity do drugiego. Upewnij się, że bieżący stan listy zakupów jest zapisywany, gdy użytkownik obróci urządzenie.

6. Podsumowanie

  • Cykl życia aktywności to zbiór stanów, przez które przechodzi aktywność, począwszy od jej utworzenia, a skończywszy na odzyskaniu przez system Android zasobów dla tej aktywności.
  • Gdy użytkownik przechodzi z jednego Activity do drugiego, a także w ramach aplikacji i poza nią, każde Activity przechodzi przez kolejne stany w swoim cyklu życia.
  • Każdy stan w cyklu życia aktywności ma odpowiednią metodę wywołania zwrotnego, którą możesz zastąpić w klasie Activity.
  • Metody cyklu życia to onCreate(), onStart(), onPause(), onRestart(), onResume(), onStop(), onDestroy().
  • Przesłonięcie metody wywołania cyklu życia umożliwia dodanie zachowania, które występuje, gdy aktywność przechodzi w dany stan.
  • W Android Studio możesz dodawać do klas metody zastępcze szkieletu za pomocą opcji Kod > Zastąpić.
  • Zmiany konfiguracji urządzenia, takie jak obracanie, powodują, że aktywność jest usuwana i powtórnie tworzona, jakby była nowa.
  • Część stanu aktywności jest zachowana po zmianie konfiguracji, w tym bieżące wartości elementów EditText. W przypadku wszystkich innych danych musisz je zapisać samodzielnie.
  • Zapisz stan instancji aktywności w metodzie onSaveInstanceState().
  • Dane stanu instancji są przechowywane w postaci prostych par klucz-wartość w pakiecie. Używaj metod pakietu do umieszczania danych w pakiecie i wybierania ich z niego.
  • Przywróć stan instancji w metodzie onCreate(), która jest preferowaną metodą, lub w metodzie onRestoreInstanceState().