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

1. Witamy

To praktyczne ćwiczenie w programowaniu jest częścią kursu 1: Pierwsze kroki z kursu Android Developer Fundamentals (wersja 2). Ten kurs przyniesie Ci najwięcej korzyści, jeśli będziesz wykonywać poniższe zadania z programowania w określonej kolejności:

  • Pełną listę ćwiczeń z programowania znajdziesz w witrynie Codelabs for Android Developer Fundamentals (V2).
  • Szczegółowe informacje o kursie, w tym linki do wszystkich rozdziałów, aplikacji i slajdów, znajdziesz w sekcji Android Developer Fundamentals (wersja 2).

Wstęp

Z tego modułu dowiesz się więcej o cyklu życia aktywności. Cykl życia to zestaw stanów, w których może występować działanie, od momentu utworzenia do zniszczenia i odzyskania zasobów przez system. Gdy użytkownik przechodzi między działaniami w aplikacji, a także z niej i z niej wychodząc, działania zmieniają się w różne stany w swoim cyklu życia.

Problem z IDouble

Każdy etap cyklu życia działania ma odpowiadającą jej metodę wywołania zwrotnego: onCreate(), onStart(), onPause() itd. Gdy działanie zmieni stan, wywoływana jest powiązana metoda wywołania zwrotnego. Znasz już jedną z tych metod: onCreate(). Jeśli zastąpisz dowolną z metod wywołania zwrotnego cyklu życia w klasach Aktywność, możesz zmienić domyślne zachowanie aktywności w odpowiedzi na działania użytkownika lub systemu.

Stan aktywności może się też zmieniać w odpowiedzi na zmiany konfiguracji urządzenia, na przykład gdy użytkownik obróci urządzenie z pionowej na poziomą. Gdy takie zmiany konfiguracji zostaną wprowadzone, aktywność zostanie zniszczona i odtworzona w stanie domyślnym, a użytkownik może utracić informacje podane w działaniu. Aby uniknąć wprowadzania użytkowników w błąd, należy opracować aplikację tak, aby zapobiec nieoczekiwanej utracie danych. W dalszej części tego ćwiczenia poeksperymentujesz ze zmianami konfiguracji i dowiesz się, jak zachowywać stan działania w odpowiedzi na zmiany konfiguracji urządzenia i inne zdarzenia cyklu życia aktywności.

W tym przykładzie dodajesz instrukcje logowania do aplikacji TwoActivities i obserwujesz zmiany cyklu życia aktywności w trakcie korzystania z aplikacji. Następnie możesz rozpocząć pracę z tymi zmianami i sprawdzić, jak postępować z danymi użytkowników w tych warunkach.

Wymagania wstępne

Potrafisz:

  • Utwórz i uruchom projekt aplikacji w Android Studio.
  • Dodaj instrukcje logowania do aplikacji i wyświetl je w panelu Logcat.
  • Rozumiej swoje działania i intencje, wykorzystuj je i wyczuwaj się na ich działania.

Czego się nauczysz

  • Jak działa cykl życia aktywności.
  • Gdy aktywność rozpoczyna się, wstrzymuje i zatrzymuje oraz zostaje skasowana.
  • Informacje o metodach wywołań zwrotnych cyklu życia związanych ze zmianami w aktywności.
  • Efekty działań (np. zmian konfiguracji), które mogą skutkować zdarzeniami cyklu życia aktywności.
  • Jak zachowywać stan aktywności w przypadku różnych zdarzeń cyklu życia.

Jakie zadania wykonasz

  • Do aplikacji TwoActivities dodaj kod z poprzedniego rozwiązania pozwalającego na zaimplementowanie różnych wywołań zwrotnych cyklu życia aktywności i uwzględnianie instrukcji rejestrowania.
  • Obserwuj zmiany stanu przy uruchomieniu aplikacji i każdej aktywności w aplikacji.
  • Zmodyfikuj swoją aplikację, aby zachować stan wystąpienia aktywności, która jest nieoczekiwanie odtworzona w odpowiedzi na zachowanie użytkownika lub zmianę konfiguracji na urządzeniu.

2. Aplikacje ogółem

W ramach tego praktycznego ćwiczenia dodajesz do aplikacji TwoActivities. Wygląda ona i działa prawie tak samo jak w poprzednich ćwiczeniach z programowania. Zawiera 2 implementacje aktywności i umożliwia użytkownikowi przesyłanie treści między nimi. Zmiany, które wprowadzisz w tej aplikacji, nie będą miały wpływu na jej widoczne zachowanie użytkownika.

3. 3. Zadanie 1. Dodaj wywołania zwrotne cyklu życia do 2 Activities

W tym zadaniu wdrożysz wszystkie metody wywołań zwrotnych cyklu życia aktywności, aby wyświetlać komunikaty do logcat po ich wywołaniu. Dzięki tym komunikatom w dzienniku możesz sprawdzać, kiedy zmienia się stan cyklu życia aktywności i jak te zmiany wpływają na działanie aplikacji.

1.1 (Opcjonalnie) Kopiowanie projektu TwoActivities

W ramach tych zadań zmodyfikujesz istniejący projekt TwoActivities utworzony w ostatnim kroku 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 otwórz element MainActivity w panelu Projekt > Android.
  2. W metodzie onCreate() dodaj następujące instrukcje logu:
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
  1. Dodaj zastąpienie wywołania zwrotnego onStart() instrukcją do logu tego zdarzenia:
@Override
public void onStart(){
    super.onStart();
    Log.d(LOG_TAG, "onStart");
}

Jako skrótu wybierz Kod > Zastąp metody w Android Studio. Pojawi się okno ze wszystkimi możliwymi metodami do zastąpienia w klasie. Wybór co najmniej 1 metody wywołania zwrotnego z listy powoduje wstawienie pełnego szablonu dla tych metod, w tym wymaganego wywołania klasy nadrzędnej.

  1. Użycie metody onStart() jako szablonu do zaimplementowania wywołań zwrotnych cyklu życia onPause(), onRestart(), onWznów(), onStop() i onDestroy()

Wszystkie metody wywołania zwrotnego mają takie same podpisy (oprócz nazwy). Jeśli skopiujesz i wklejysz metodę onStart(), aby utworzyć inne metody wywołania zwrotnego, nie zapomnij zaktualizować zawartości, aby wywołać odpowiednią metodę w klasie nadrzędnej i zarejestrować prawidłową metodę.

  1. Uruchom aplikację.
  2. Kliknij kartę Logcat u dołu Android Studio, aby wyświetlić panel Logcat. Powinny się wyświetlić 3 komunikaty z informacjami o 3 stanach cyklu życia, przez które aktywność została przeniesiona w momencie rozpoczęcia:
D/MainActivity: -------
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: onResume

1.3 Wdrażanie wywołań zwrotnych cyklu życia w DrugiAktywność

Po zaimplementowaniu metod wywołań zwrotnych cyklu życia dla elementu MainActivity zrób to samo w przypadku obiektu SecondActivity.

  1. Otwórz SecondActivity.
  2. Na górze klasy dodaj stałą dla zmiennej LOG_TAG:
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
  1. Do drugiej aktywności dodaj wywołania zwrotne cyklu życia i instrukcje rejestrowania. Metody wywołania zwrotnego możesz skopiować i wkleić z MainActivity.
  2. Dodaj instrukcję logu do metody ReturnReply() tuż przed metodą complete():
Log.d(LOG_TAG, "End SecondActivity");

1.4 Obserwowanie dziennika podczas uruchamiania aplikacji**

  1. Uruchom aplikację.
  2. Kliknij kartę Logcat u dołu Android Studio, aby wyświetlić panel Logcat.
  3. W polu wyszukiwania wpisz aktywność. Dziennik na Androidzie może być bardzo długi i niechciany. Zmienna LOG_TAG w każdej klasie zawiera słowa MainActivity lub SecondActivity, dlatego to słowo kluczowe umożliwia filtrowanie dziennika wyłącznie pod kątem interesujących Cię informacji.

Problem z IDouble

Eksperymentuj z użyciem aplikacji. Zauważ, że zdarzenia cyklu życia, które występują w odpowiedzi na różne działania, W szczególności spróbuj:

  • Korzystaj z aplikacji normalnie (wyślij wiadomość, odpowiedz inną wiadomością).
  • Użyj przycisku Wstecz, aby wrócić z drugiego modułu do głównej aktywności.
  • Użyj strzałki w górę na pasku aplikacji, aby wrócić z drugiej aktywności do głównej.
  • Obracaj urządzenie zarówno podczas głównego, jak i drugiego etapu aktywności w różnych momentach w aplikacji i obserwuj, co dzieje się w * dzienniku i na ekranie.
  • Naciśnij przycisk przeglądu (kwadratowy przycisk po prawej stronie ekranu głównego) i zamknij aplikację (kliknij X).
  • Wróć do ekranu głównego i ponownie uruchom aplikację.

WSKAZÓWKA: jeśli korzystasz z aplikacji w emulatorze, możesz przeprowadzić symulację obrotu, naciskając Ctrl+F11 lub Control+Function+F11.

Kod rozwiązania zadania 1

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

MainActivity

Poniższe fragmenty kodu pokazują dodany kod w elemencie MainActivity, ale nie przez 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 elemencie DrugActivity, 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:

Tak samo jak w przypadku strony MainActivity.

4. 4. Zadanie 2. Zapisz i przywróć stan instancji aktywności

W zależności od zasobów systemowych i zachowania użytkowników każda aktywność w aplikacji może zostać niszczona i odtworzona znacznie częściej, niż Ci się wydaje.

W ostatniej sekcji, gdy obróciłeś urządzenie lub emulator, mogło Ci się to przydać. Jednym z przykładów zmian w jego konfiguracji jest obrócenie urządzenia. Rotacja jest najczęstszą, ale wszystkie zmiany w konfiguracji powodują zniszczenie bieżącej aktywności i odtworzenie jej tak, jakby była nowa. Jeśli nie uwzględnisz takiego zachowania w kodzie, po wprowadzeniu zmian w 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 w postaci zestawu par klucz-wartość w obiekcie pakietu nazywanym stanem wystąpienia aktywności. System zapisuje domyślne informacje o stanie w postaci pakietu stanu instancji tuż przed zatrzymaniem aktywności i przekazuje ten pakiet do nowej instancji aktywności, aby go przywrócić.

Aby zapobiec utracie danych w działaniu, gdy zostaną one nieoczekiwanie zniszczone i odtworzone, musisz wdrożyć metodę onSaveInstanceState(). System wywołuje tę metodę w Twojej aktywności (między onPause() i onStop()), gdy istnieje możliwość, że może ona zostać zniszczona i odtworzona.

Dane zapisywane w stanie instancji dotyczą tylko tego konkretnego wystąpienia w bieżącej sesji aplikacji. Gdy zatrzymasz i ponownie uruchomisz nową sesję w aplikacji, stan wystąpienia aktywności zostanie utracony i aktywność zostanie przywrócona do domyślnego wyglądu. Jeśli musisz zapisywać dane użytkowników między sesjami aplikacji, użyj wspólnych preferencji lub bazy danych. Oba te zagadnienia omówimy w późniejszej części praktycznej.

2.1 Zapisz stan instancji aktywności za pomocą funkcji onSaveInstanceState()

Jak można zauważyć, obrót urządzenia w ogóle nie ma wpływu na stan drugiego działania. Dzieje się tak, ponieważ drugi układ i stan aktywności są generowane na podstawie szablonu i intencji, która go aktywowała. Nawet jeśli aktywność zostanie odtworzona, intencja nadal istnieje, a dane w tej intencji są nadal używane za każdym razem, gdy zostanie wywołana metoda onCreate() w drugim działaniu.

Dodatkowo możesz zauważyć, że w każdej aktywności każdy 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 View w układzie są automatycznie zapisywane po wszystkich zmianach w konfiguracji, a obecna wartość EditText jest jednym z takich przypadków.

Dlatego jedynym stanem aktywności, który Cię interesuje, są elementy TextView dla nagłówka odpowiedzi i tekst odpowiedzi w głównej aktywności. Oba elementy TextView są domyślnie niewidoczne – pojawiają się tylko wtedy, gdy wyślesz wiadomość z powrotem do głównej aktywności z poziomu drugiego działania.

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

  1. Otwórz MainActivity.
  2. Dodaj tę szkieletową implementację funkcji onSaveInstanceState() do aktywności lub użyj opcji Kod > Zastąp metody, by wstawić szkielet zastąpienia.
@Override
public void onSaveInstanceState(Bundle outState) {
          super.onSaveInstanceState(outState);
}
  1. Sprawdź, czy nagłówek jest obecnie widoczny. Jeśli tak, umieść ten stan widoczności w pakiecie 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 będą oznaczone jako niewidoczne do czasu wysłania odpowiedzi z drugiej aktywności. Jeśli nagłówek jest widoczny, musisz zapisać dane odpowiedzi. Interesuje nas tylko ten stan widoczności – nie trzeba zapisywać tekstu nagłówka, ponieważ ten tekst nigdy się nie zmienia.

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

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

Zapisujesz tylko te elementy widoku, które mogą się zmienić po utworzeniu aktywności. Pozostałe elementy widoku danych w aplikacji (EditText, Button) można w każdej chwili odtworzyć z układu domyślnego.

Zwróć uwagę, że system zapisuje stan niektórych elementów View, np. zawartości elementu EditText.

2.2 Przywracanie stanu instancji aktywności w onCreate()

Po zapisaniu stanu wystąpienia aktywności musisz go przywrócić po odtworzeniu aktywności. Możesz to zrobić w funkcji onCreate() lub za pomocą wywołania zwrotnego onRestoreInstanceState(), które jest wywoływane po utworzeniu działania po utworzeniu działania.

Najczęściej lepszym miejscem na przywrócenie stanu działania jest onCreate(), co zapewnia jak najszybsze udostępnienie interfejsu i stanu działania. Czasami warto zrobić to w metodzie onRestoreInstanceState() po zakończeniu inicjowania. Możesz też pozwolić podklasom zdecydować, czy użyć implementacji domyślnej.

  1. W metodzie onCreate() po zainicjowaniu zmiennych widoku danych za pomocą metody findViewById() dodaj test, aby się upewnić, że savedInstanceState nie ma wartości 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) {
}

Po utworzeniu aktywności system przekazuje pakiet stanu do onCreate() jako jedyny argument. Przy pierwszym wywołaniu funkcji onCreate() i uruchomieniu aplikacji pakiet ma wartość null – nie ma żadnego stanu przy pierwszym uruchomieniu aplikacji. Kolejne wywołania onCreate() zawierają pakiet zawierający dane przechowywane w onSaveInstanceState().

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

Jeśli w pakiecie stanu znajduje się klucz response_visible (a w tym przypadku parametr isvisible ma wartość prawda), musisz przywrócić ten stan.

  1. Zadbaj o widoczność nagłówka w ramach testu isvisible.
mReplyHeadTextView.setVisibility(View.VISIBLE);
  1. Odbierz z pakietu odpowiedź tekstową z kluczem „reply_text”, a następnie ustaw w TextView odpowiedzi wyświetlanie tego ciągu.
mReplyTextView.setText(savedInstanceState.getString("reply_text"));
  1. Włącz także widoczność TextView odpowiedzi:
mReplyTextView.setVisibility(View.VISIBLE);
  1. Uruchom aplikację. Obróć urządzenie lub emulator, aby mieć pewność, że wiadomość z odpowiedzią (jeśli jest widoczna) pozostanie na ekranie po odtworzeniu aktywności.

Kod rozwiązania zadania 2

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

MainActivity

Poniższe fragmenty kodu pokazują dodany kod w elemencie MainActivity, ale nie przez 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);
       }
   }
}

Cały projekt:

Projekt Android Studio: TwoActivitiesLifecycle

5. Kodowanie

Wyzwanie: stwórz prostą aplikację z listą zakupów, która będzie zawierać główne działanie dla listy tworzonej przez użytkownika, a drugie – listę popularnych produktów.

  • Główne działanie powinno zawierać listę do utworzenia, która powinna się składać z dziesięciu pustych elementów TextView.
  • Przycisk Dodaj element w głównej aktywności uruchamia drugie działanie, które zawiera listę najczęściej używanych produktów (ser, ryż, jabłka itd.). Do wyświetlania elementów służą przyciski.
  • Wybranie elementu powoduje powrót do głównej aktywności użytkownika i aktualizowanie pustego obiektu TextView z uwzględnieniem wybranego elementu.

Intencja przekazywania informacji między działaniami. 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 zestaw stanów, przez które przechodzi aktywność, od momentu jej utworzenia do momentu, gdy system Android odzyska zasoby tej aktywności.
  • Gdy użytkownik przechodzi z jednej aktywności do drugiej oraz wewnątrz i poza nią, każda aktywność przechodzi między stanami w cyklu życia.
  • Każdy stan w cyklu życia aktywności ma odpowiadającą jej metodę wywołania zwrotnego, którą możesz zastąpić w klasie Aktywność.
  • Metody cyklu życia to onCreate(), onStart(), onPause(), onRestart(), onWznów(), onStop(), onDestroy().
  • Zastąpienie metody wywołania zwrotnego cyklu życia pozwala dodać zachowanie, które następuje, gdy aktywność przejdzie w ten stan.
  • Możesz dodawać szkieletowe metody zastępowania do klas w Android Studio, klikając Kod > Zastąp.
  • Zmiany w konfiguracji urządzenia, takie jak rotacja, powodują zniszczenie aktywności i odtwarzanie jej tak, jakby była nowa.
  • Część stanu aktywności jest zachowywana po zmianie konfiguracji, w tym bieżące wartości elementów EditText. Pozostałe dane musisz zapisać samodzielnie.
  • Zapisz stan instancji aktywności w metodzie onSaveInstanceState().
  • Dane o stanie instancji są przechowywane w pakiecie jako proste pary klucz-wartość. Metoda Bundle pozwala umieścić dane w pakiecie i wycofać je z niego.
  • Aby przywrócić stan instancji, użyj funkcji onCreate() (co jest preferowaną metodą) lub onRestoreInstanceState().