Android Kotlin Fundamentals 04.2: Complex lifecycle situations

Ten moduł Codelab jest częścią kursu Android Kotlin Fundamentals. Najwięcej korzyści przyniesie Ci ukończenie wszystkich ćwiczeń w kolejności. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej kursu Android Kotlin Fundamentals.

Wprowadzenie

W ostatnim laboratorium kodowym omówiliśmy cykle życia ActivityFragment oraz metody wywoływane w przypadku zmiany stanu cyklu życia w aktywnościach i fragmentach. W tym laboratorium dowiesz się więcej o cyklu życia aktywności. Dowiesz się też o bibliotece cyklu życia Androida Jetpack, która pomaga zarządzać zdarzeniami cyklu życia za pomocą lepiej zorganizowanego i łatwiejszego w utrzymaniu kodu.

Co warto wiedzieć

  • Co to jest aktywność i jak ją utworzyć w aplikacji.
  • Podstawy cykli życia ActivityFragment oraz wywołania zwrotne, które są wywoływane, gdy aktywność przechodzi między stanami.
  • Jak zastąpić metody wywołania zwrotnego cyklu życia onCreate()onStop(), aby wykonywać operacje w różnych momentach cyklu życia działania lub fragmentu.

Czego się nauczysz

  • Jak konfigurować, uruchamiać i zatrzymywać części aplikacji w wywołaniach zwrotnych cyklu życia.
  • Jak używać biblioteki cyklu życia Androida do tworzenia obserwatora cyklu życia i ułatwiać zarządzanie cyklem życia aktywności i fragmentów.
  • Jak zamknięcia procesów Androida wpływają na dane w aplikacji oraz jak automatycznie zapisywać i przywracać te dane, gdy Android zamyka aplikację.
  • Jak obracanie urządzenia i inne zmiany konfiguracji wpływają na stany cyklu życia i stan aplikacji.

Co musisz zrobić

  • Zmodyfikuj aplikację DessertClicker, aby zawierała funkcję timera, i uruchamiaj oraz zatrzymuj ten timer w różnych momentach cyklu życia aktywności.
  • Zmodyfikuj aplikację, aby korzystała z biblioteki cyklu życia Androida, i przekształć klasę DessertTimer w obserwatora cyklu życia.
  • Skonfiguruj i używaj Android Debug Bridge (adb), aby symulować zamknięcie procesu aplikacji i wywołania zwrotne cyklu życia, które wtedy występują.
  • Wdróż metodę onSaveInstanceState(), aby zachować dane aplikacji, które mogą zostać utracone w przypadku jej nieoczekiwanego zamknięcia. Dodaj kod, który przywróci te dane po ponownym uruchomieniu aplikacji.

W tych ćwiczeniach z programowania rozwiniesz aplikację DessertClicker z poprzednich ćwiczeń. Dodajesz licznik czasu w tle, a potem przekształcasz aplikację, aby korzystała z biblioteki cyklu życia Androida.

W poprzednich ćwiczeniach z programowania dowiedzieliśmy się, jak obserwować aktywność i cykle życia fragmentów, zastępując różne wywołania zwrotne cyklu życia i rejestrując, kiedy system je wywołuje. W tym ćwiczeniu poznasz bardziej złożony przykład zarządzania zadaniami związanymi z cyklem życia w aplikacji DessertClicker. Użyjesz timera, który co sekundę wyświetla instrukcję logowania z liczbą sekund, przez które działa.

Krok 1. Skonfiguruj aplikację DessertTimer

  1. Otwórz aplikację DessertClicker z ostatnich zajęć. (Jeśli nie masz aplikacji, możesz pobrać pliki DessertClickerLogs tutaj).
  2. W widoku Project (Projekt) rozwiń kolejno java > com.example.android.dessertclicker i otwórz DessertTimer.kt. Zwróć uwagę, że cały kod jest obecnie wykomentowany, więc nie jest uruchamiany w ramach aplikacji.
  3. Zaznacz cały kod w oknie edytora. Wybierz Code (Kod) > Comment with Line Comment (Skomentuj wiersz) lub naciśnij Control+/ (Command+/ na komputerze Mac). To polecenie usuwa komentarze z całego kodu w pliku. (Android Studio może wyświetlać błędy nierozwiązanych odwołań, dopóki nie ponownie nie skompilujesz aplikacji).
  4. Zwróć uwagę, że klasa DessertTimer zawiera elementy startTimer()stopTimer(), które uruchamiają i zatrzymują stoper. Gdy program startTimer() jest uruchomiony, licznik czasu co sekundę wyświetla komunikat dziennika z całkowitą liczbą sekund, przez które był uruchomiony. Metoda stopTimer() zatrzymuje minutnik i instrukcje logowania.
  1. Otwórz pokój MainActivity.kt. U góry klasy, tuż pod zmienną dessertsSold, dodaj zmienną timera:
private lateinit var dessertTimer : DessertTimer;
  1. Przewiń w dół do sekcji onCreate() i utwórz nowy obiekt DessertTimer tuż po wywołaniu funkcji setOnClickListener():
dessertTimer = DessertTimer()


Teraz, gdy masz już obiekt timera deseru, zastanów się, gdzie należy rozpocząć i zatrzymać odliczanie, aby działał on tylko wtedy, gdy aktywność jest widoczna na ekranie. W kolejnych krokach przyjrzysz się kilku opcjom.

Krok 2. Uruchom i zatrzymaj minutnik

Metoda onStart() jest wywoływana tuż przed tym, jak aktywność staje się widoczna. Metoda onStop() jest wywoływana po tym, jak działanie przestaje być widoczne. Te wywołania zwrotne wydają się odpowiednie do rozpoczęcia i zatrzymania timera.

  1. W klasie MainActivity uruchom minutnik w wywołaniu zwrotnym onStart():
override fun onStart() {
   super.onStart()
   dessertTimer.startTimer()

   Timber.i("onStart called")
}
  1. Zatrzymaj minutnik za onStop():
override fun onStop() {
   super.onStop()
   dessertTimer.stopTimer()

   Timber.i("onStop Called")
}
  1. Skompiluj i uruchom aplikację. W Android Studio kliknij panel Logcat. W polu wyszukiwania Logcat wpisz dessertclicker, aby filtrować według klas MainActivityDessertTimer. Zauważ, że po uruchomieniu aplikacji licznik czasu również zaczyna działać natychmiast.
  2. Kliknij przycisk Wstecz. Zobaczysz, że minutnik ponownie się zatrzymał. Licznik czasu zatrzymuje się, ponieważ aktywność i sterowany przez nią licznik czasu zostały usunięte.
  3. Użyj ekranu ostatnich aplikacji, aby wrócić do aplikacji. Zwróć uwagę, że w narzędziu Logcat licznik czasu uruchamia się ponownie od 0.
  4. Kliknij przycisk Udostępnij. Zwróć uwagę, że w Logcat minutnik nadal działa.

  5. Kliknij przycisk ekranu głównego. Zwróć uwagę, że w Logcat minutnik przestaje działać.
  6. Użyj ekranu ostatnich aplikacji, aby wrócić do aplikacji. Zwróć uwagę, że w Logcat zegar zaczyna odliczać od miejsca, w którym został zatrzymany.
  7. W pliku MainActivity w metodzie onStop() zmień w komentarz wywołanie funkcji stopTimer(). Zakomentowanie stopTimer() pokazuje sytuację, w której rozpoczynasz operację w onStart(), ale zapominasz ją zatrzymać w onStop().
  8. Skompiluj i uruchom aplikację, a po rozpoczęciu odliczania kliknij przycisk ekranu głównego. Nawet jeśli aplikacja działa w tle, minutnik jest włączony i stale wykorzystuje zasoby systemowe. Dalsze działanie timera powoduje wyciek pamięci w aplikacji i prawdopodobnie nie jest pożądanym zachowaniem.

    Ogólna zasada jest taka, że jeśli coś skonfigurujesz lub uruchomisz w wywołaniu zwrotnym, musisz to zatrzymać lub usunąć w odpowiednim wywołaniu zwrotnym. Dzięki temu unikniesz sytuacji, w której coś działa, gdy nie jest już potrzebne.
  1. Usuń znacznik komentarza z wiersza w pliku onStop(), w którym zatrzymujesz minutnik.
  2. Wytnij i wklej połączenie startTimer() z onStart() do onCreate(). Ta zmiana pokazuje przypadek, w którym zasób jest inicjowany i uruchamiany w onCreate(), zamiast inicjować go w onCreate() i uruchamiać w onStart().
  3. Skompiluj i uruchom aplikację. Zegar zacznie odliczać czas zgodnie z oczekiwaniami.
  4. Kliknij Home, aby zatrzymać aplikację. Minutnik zatrzyma się zgodnie z oczekiwaniami.
  5. Użyj ekranu ostatnich aplikacji, aby wrócić do aplikacji. Zwróć uwagę, że w tym przypadku licznik czasu nie uruchamia się ponownie, ponieważ funkcja onCreate() jest wywoływana tylko wtedy, gdy aplikacja się uruchamia – nie jest wywoływana, gdy aplikacja wraca na pierwszy plan.

Najważniejsze informacje:

  • Jeśli skonfigurujesz zasób w wywołaniu zwrotnym cyklu życia, musisz go też usunąć.
  • Skonfiguruj i zakończ działanie w odpowiednich metodach.
  • Jeśli coś skonfigurujesz w onStart(), możesz to zatrzymać lub usunąć w onStop().

W aplikacji DessertClicker łatwo zauważyć, że jeśli timer został uruchomiony w onStart(), to należy go zatrzymać w onStop(). Jest tylko jeden minutnik, więc zatrzymanie go nie jest trudne do zapamiętania.

W bardziej złożonej aplikacji na Androida możesz skonfigurować wiele elementów w funkcjach onStart() lub onCreate(), a następnie usunąć je w funkcjach onStop() lub onDestroy(). Możesz na przykład mieć animacje, muzykę, czujniki lub minutniki, które musisz skonfigurować i wyłączyć, a także uruchomić i zatrzymać. Jeśli o którymś zapomnisz, może to prowadzić do błędów i problemów.

Biblioteka cyklu życia, która jest częścią Androida Jetpack, upraszcza to zadanie. Biblioteka jest szczególnie przydatna w przypadkach, gdy musisz śledzić wiele ruchomych elementów, z których niektóre znajdują się w różnych stanach cyklu życia. Biblioteka odwraca sposób działania cykli życia: zwykle aktywność lub fragment informuje komponent (np. DessertTimer), co ma zrobić, gdy nastąpi wywołanie zwrotne cyklu życia. Gdy jednak używasz biblioteki cyklu życia, komponent sam śledzi zmiany cyklu życia, a potem wykonuje niezbędne działania, gdy te zmiany nastąpią.

Biblioteka cyklu życia składa się z 3 głównych części:

  • Właściciele cyklu życia, czyli komponenty, które mają cykl życia (i dlatego są jego „właścicielami”). Użytkownicy ActivityFragment są właścicielami cyklu życia. Właściciele cyklu życia implementują interfejs LifecycleOwner.
  • Klasa Lifecycle, która zawiera rzeczywisty stan właściciela cyklu życia i uruchamia zdarzenia, gdy nastąpią zmiany w cyklu życia.
  • Obserwatorzy cyklu życia, którzy obserwują stan cyklu życia i wykonują zadania, gdy cykl życia się zmienia. Obserwatorzy cyklu życia implementują interfejs LifecycleObserver.

W tym zadaniu przekształcisz aplikację DessertClicker, aby korzystała z biblioteki cyklu życia Androida, i dowiesz się, jak ta biblioteka ułatwia zarządzanie cyklami życia aktywności i fragmentów Androida.

Krok 1. Zmień klasę DessertTimer w LifecycleObserver

Kluczowym elementem biblioteki cyklu życia jest koncepcja obserwacji cyklu życia. Obserwacja umożliwia klasom (np. DessertTimer) poznanie aktywności lub cyklu życia fragmentu oraz samodzielne rozpoczynanie i zatrzymywanie działania w odpowiedzi na zmiany tych stanów cyklu życia. Dzięki obserwatorowi cyklu życia możesz usunąć z metod aktywności i fragmentów odpowiedzialność za uruchamianie i zatrzymywanie obiektów.

  1. Otwórz zajęcia DesertTimer.kt.
  2. Zmień sygnaturę klasy DessertTimer, aby wyglądała tak:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

Ta nowa definicja klasy wykonuje 2 czynności:

  • Konstruktor przyjmuje obiekt Lifecycle, który jest cyklem życia obserwowanym przez timer.
  • Definicja klasy implementuje interfejs LifecycleObserver.
  1. Pod zmienną runnable dodaj blok init do definicji klasy. W bloku init użyj metody addObserver(), aby połączyć obiekt cyklu życia przekazany od właściciela (aktywności) z tą klasą (obserwatorem).
 init {
   lifecycle.addObserver(this)
}
  1. Dodaj adnotację startTimer() za pomocą @OnLifecycleEvent annotation i użyj zdarzenia cyklu życia ON_START. Wszystkie zdarzenia cyklu życia, które może obserwować obserwator cyklu życia, znajdują się w klasie Lifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. Zrób to samo w przypadku stopTimer(), używając zdarzenia ON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

Krok 2. Zmodyfikuj plik MainActivity

Klasa MainActivity jest już właścicielem cyklu życia przez dziedziczenie, ponieważ superklasa FragmentActivity implementuje interfejs LifecycleOwner. Nie musisz więc nic robić, aby aktywność była zgodna z cyklem życia. Wystarczy przekazać obiekt cyklu życia aktywności do konstruktora DessertTimer.

  1. Otwórz pokój MainActivity. W metodzie onCreate() zmodyfikuj inicjowanie DessertTimer, aby uwzględnić this.lifecycle:
dessertTimer = DessertTimer(this.lifecycle)

Właściwość lifecycle aktywności zawiera obiekt Lifecycle, którego właścicielem jest ta aktywność.

  1. Usuń połączenie z numerem startTimer()onCreate() i połączenie z numerem stopTimer()onStop(). Nie musisz już informować DessertTimer o tym, co ma robić w aktywności, ponieważ DessertTimer obserwuje teraz cykl życia i jest automatycznie powiadamiany o zmianach stanu cyklu życia. W tych wywołaniach zwrotnych wystarczy teraz zalogować wiadomość.
  2. Skompiluj i uruchom aplikację, a następnie otwórz Logcat. Zauważ, że minutnik zaczął działać zgodnie z oczekiwaniami.
  3. Kliknij przycisk ekranu głównego, aby przenieść aplikację w tło. Zauważ, że minutnik przestał działać zgodnie z oczekiwaniami.

Co się stanie z aplikacją i jej danymi, jeśli Android zamknie ją w tle? Warto zrozumieć ten skomplikowany przypadek brzegowy.

Gdy aplikacja przechodzi w tło, nie jest zamykana, tylko zatrzymywana i czeka na powrót użytkownika. Jednym z głównych zadań systemu Android jest jednak zapewnienie płynnego działania aktywności na pierwszym planie. Jeśli na przykład użytkownik korzysta z aplikacji GPS, aby złapać autobus, ważne jest, aby szybko ją wyświetlić i dalej pokazywać wskazówki. Mniej ważne jest, aby aplikacja DessertClicker, z której użytkownik nie korzystał od kilku dni, działała płynnie w tle.

Android reguluje działanie aplikacji w tle, aby aplikacja na pierwszym planie mogła działać bez problemów. Na przykład Android ogranicza ilość przetwarzania, jaką mogą wykonywać aplikacje działające w tle.

Czasami Android zamyka cały proces aplikacji, w tym wszystkie działania z nią powiązane. Dzieje się tak, gdy system jest obciążony i istnieje ryzyko opóźnień wizualnych. W takiej sytuacji nie są wykonywane żadne dodatkowe wywołania zwrotne ani kod. Proces aplikacji jest po prostu zamykany w tle bez powiadomienia. Użytkownik nie widzi jednak, że aplikacja została zamknięta. Gdy użytkownik wróci do aplikacji, którą system Android zamknął, system ponownie ją uruchomi.

W tym zadaniu symulujesz zamknięcie procesu Androida i sprawdzasz, co się stanie z aplikacją, gdy uruchomi się ponownie.

Krok 1. Użyj adb, aby zasymulować zamknięcie procesu

Android Debug Bridge (adb) to narzędzie wiersza poleceń, które umożliwia wysyłanie instrukcji do emulatorów i urządzeń podłączonych do komputera. W tym kroku użyjesz przycisku adb, aby zamknąć proces aplikacji i sprawdzić, co się stanie, gdy Android zamknie aplikację.

  1. Skompiluj i uruchom aplikację. Kliknij babeczkę kilka razy.
  2. Naciśnij przycisk ekranu głównego, aby przenieść aplikację w tle. Aplikacja zostanie zatrzymana i może zostać zamknięta, jeśli Android będzie potrzebować zasobów, z których korzysta.
  3. W Android Studio kliknij kartę Terminal, aby otworzyć terminal wiersza poleceń.
  4. Wpisz adb i naciśnij Return.

    Jeśli zobaczysz wiele wyników zaczynających się od Android Debug Bridge version X.XX.X i kończących się na tags to be used by logcat (see logcat —help), wszystko jest w porządku. Jeśli zamiast tego widzisz adb: command not found, sprawdź, czy polecenie adb jest dostępne w ścieżce wykonywania. Instrukcje znajdziesz w sekcji „Dodawanie adb do ścieżki wykonywania” w  rozdziale Narzędzia.
  5. Skopiuj ten komentarz i wklej go w wierszu poleceń, a następnie naciśnij Return:
adb shell am kill com.example.android.dessertclicker

To polecenie nakazuje wszystkim połączonym urządzeniom lub emulatorom zatrzymanie procesu o nazwie pakietu dessertclicker, ale tylko wtedy, gdy aplikacja działa w tle. Ponieważ aplikacja działała w tle, na ekranie urządzenia lub emulatora nie widać żadnych informacji o zatrzymaniu procesu. W Android Studio kliknij kartę Uruchom, aby zobaczyć komunikat „Application terminated” (Aplikacja została zamknięta). Kliknij kartę Logcat, aby zobaczyć, że wywołanie zwrotne onDestroy() nigdy nie zostało uruchomione – Twoja aktywność po prostu się zakończyła.

  1. Aby wrócić do aplikacji, użyj ekranu ostatnich aplikacji. Aplikacja pojawi się na ekranie ostatnich aplikacji niezależnie od tego, czy została przeniesiona w tle, czy całkowicie zatrzymana. Gdy użyjesz ekranu ostatnich aplikacji, aby wrócić do aplikacji, aktywność zostanie ponownie uruchomiona. Działanie przechodzi przez cały zestaw wywołań zwrotnych cyklu życia uruchamiania, w tym onCreate().
  2. Zwróć uwagę, że po ponownym uruchomieniu aplikacji „wynik” (liczba sprzedanych deserów i łączna kwota w dolarach) zostaje zresetowany do wartości domyślnych (0). Jeśli Android zamknął aplikację, dlaczego nie zapisał jej stanu?

    Gdy system operacyjny ponownie uruchamia aplikację, Android dokłada wszelkich starań, aby przywrócić ją do stanu, w jakim była wcześniej. Android zapisuje stan niektórych widoków w pakiecie za każdym razem, gdy opuszczasz aktywność. Przykłady danych, które są zapisywane automatycznie, to tekst w widżecie EditText (o ile ma on identyfikator ustawiony w układzie) i stos wsteczny aktywności.

    Czasami jednak system Android nie zna wszystkich Twoich danych. Jeśli np. w aplikacji DessertClicker masz zmienną niestandardową revenue, system operacyjny Android nie wie o tych danych ani o ich znaczeniu dla Twojej aktywności. Musisz samodzielnie dodać te dane do pakietu.

Krok 2. Użyj metody onSaveInstanceState() do zapisania danych pakietu

Metoda onSaveInstanceState() to wywołanie zwrotne, którego używasz do zapisywania danych, które mogą być potrzebne, jeśli system Android zniszczy Twoją aplikację. Na diagramie wywołań zwrotnych cyklu życia metoda onSaveInstanceState() jest wywoływana po zatrzymaniu aktywności. Jest ona wywoływana za każdym razem, gdy aplikacja przechodzi w tło.

Wywołanie onSaveInstanceState() to środek bezpieczeństwa. Daje Ci możliwość zapisania niewielkiej ilości informacji w pakiecie, gdy Twoja aktywność przestaje być widoczna na pierwszym planie. System zapisuje te dane teraz, ponieważ gdyby czekał na zamknięcie aplikacji, system operacyjny mógłby być pod presją zasobów. Zapisywanie danych za każdym razem zapewnia, że dane aktualizacji w pakiecie są dostępne do przywrócenia w razie potrzeby.

  1. MainActivity zastąp wywołanie zwrotne onSaveInstanceState() i dodaj instrukcję logowania Timber.
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Timber.i("onSaveInstanceState Called")
}
  1. Skompiluj i uruchom aplikację, a następnie kliknij przycisk Home, aby przenieść ją w tle. Zwróć uwagę, że wywołanie zwrotne onSaveInstanceState() następuje tuż po onPause()onStop():
  2. U góry pliku, tuż przed definicją klasy, dodaj te stałe:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"

Będziesz używać tych kluczy zarówno do zapisywania, jak i pobierania danych z pakietu stanu instancji.

  1. Przewiń w dół do onSaveInstanceState() i zwróć uwagę na parametr outState, który jest typu Bundle.

    Pakiet to zbiór par klucz-wartość, w których klucze są zawsze ciągami znaków. Do pakietu możesz wstawiać wartości pierwotne, np. wartości intboolean.
    Ponieważ system przechowuje ten pakiet w pamięci RAM, zalecamy, aby dane w pakiecie były niewielkie. Rozmiar tego pakietu jest również ograniczony, ale różni się w zależności od urządzenia. Zazwyczaj należy przechowywać znacznie mniej niż 100 tys. wartości, w przeciwnym razie aplikacja może ulec awarii z powodu błędu TransactionTooLargeException.
  2. onSaveInstanceState() umieść wartość revenue (liczbę całkowitą) w pakiecie za pomocą metody putInt():
outState.putInt(KEY_REVENUE, revenue)

Metoda putInt() (i podobne metody z klasy Bundle, takie jak putFloat()putString()) przyjmuje 2 argumenty: ciąg znaków klucza (stałą KEY_REVENUE) i rzeczywistą wartość do zapisania.

  1. Powtórz ten sam proces w przypadku liczby sprzedanych deserów i stanu timera:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

Krok 3. Użyj metody onCreate(), aby przywrócić dane pakietu

  1. Przewiń w górę do onCreate() i sprawdź sygnaturę metody:
override fun onCreate(savedInstanceState: Bundle) {

Zwróć uwagę, że funkcja onCreate() otrzymuje wartość Bundle za każdym razem, gdy jest wywoływana. Gdy aktywność zostanie ponownie uruchomiona z powodu zamknięcia procesu, zapisany pakiet zostanie przekazany do onCreate(). Jeśli Twoja aktywność zaczynała się od nowa, ten pakiet w onCreate() to null. Jeśli pakiet nie jest null, wiesz, że „odtwarzasz” aktywność od znanego wcześniej punktu.

  1. Dodaj ten kod do pliku onCreate() po skonfigurowaniu DessertTimer:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

Test null sprawdza, czy w pakiecie są dane, czy jest on null. Na tej podstawie możesz stwierdzić, czy aplikacja została uruchomiona od nowa, czy też została ponownie utworzona po zamknięciu. Ten test jest typowym wzorcem przywracania danych z pakietu.

Zauważ, że klucz użyty tutaj (KEY_REVENUE) jest taki sam jak klucz użyty w przypadku putInt(). Aby mieć pewność, że za każdym razem używasz tego samego klucza, najlepiej zdefiniować te klucze jako stałe. Aby pobrać dane z pakietu, używasz getInt(), tak jak w przypadku umieszczania danych w pakiecie używasz putInt(). Metoda getInt() przyjmuje 2 argumenty:

  • Ciąg znaków, który pełni rolę klucza, np. "key_revenue" w przypadku wartości przychodu.
  • Wartość domyślna, jeśli w pakiecie nie ma wartości dla danego klucza.

Liczba całkowita uzyskana z pakietu jest następnie przypisywana do zmiennej revenue, a interfejs użytkownika używa tej wartości.

  1. Dodaj metody getInt(), aby przywrócić liczbę sprzedanych deserów i wartość timera:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount =
       savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}
  1. Skompiluj i uruchom aplikację. Naciśnij babeczkę co najmniej 5 razy, aż zmieni się w donuta. Kliknij Home, aby przenieść aplikację w tle.
  2. Na karcie Terminal w Android Studio uruchom polecenie adb, aby zamknąć proces aplikacji.
adb shell am kill com.example.android.dessertclicker
  1. Użyj ekranu ostatnich aplikacji, aby wrócić do aplikacji. Zwróć uwagę, że tym razem aplikacja zwraca prawidłowe wartości przychodów i sprzedanych deserów z pakietu. Zwróć też uwagę, że deser znowu jest babeczką. Aby mieć pewność, że aplikacja zostanie przywrócona po wyłączeniu dokładnie w takim stanie, w jakim została pozostawiona, musisz wykonać jeszcze jedną czynność.
  2. W MainActivity sprawdź metodę showCurrentDessert(). Zwróć uwagę, że ta metoda określa, który obraz deseru ma być wyświetlany w aktywności, na podstawie aktualnej liczby sprzedanych deserów i listy deserów w zmiennej allDesserts.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

Ta metoda opiera się na liczbie sprzedanych deserów, aby wybrać odpowiedni obraz. Dlatego nie musisz nic robić, aby zapisać odwołanie do obrazu w pakiecie w onSaveInstanceState(). W tym pakiecie przechowujesz już liczbę sprzedanych deserów.

  1. W pliku onCreate() w bloku, który przywraca stan z pakietu, wywołaj funkcję showCurrentDessert():
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount = 
      savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
   showCurrentDessert()                   
}
  1. Skompiluj i uruchom aplikację, a następnie umieść ją w tle. Aby zakończyć proces, użyj adb. Użyj ekranu ostatnich aplikacji, aby wrócić do aplikacji. Zwróć uwagę, że wartości dotyczące sprzedanych deserów, łącznych przychodów i obrazu deseru zostały prawidłowo przywrócone.

W zarządzaniu cyklem życia aktywności i fragmentów występuje jeszcze jeden szczególny przypadek, który warto poznać: jak zmiany konfiguracji wpływają na cykl życia aktywności i fragmentów.

Zmiana konfiguracji następuje, gdy stan urządzenia zmienia się tak radykalnie, że najprostszym sposobem rozwiązania problemu jest całkowite zamknięcie i ponowne utworzenie aktywności. Jeśli na przykład użytkownik zmieni język urządzenia, cały układ może wymagać zmiany, aby dostosować się do różnych kierunków tekstu. Jeśli użytkownik podłączy urządzenie do stacji dokującej lub doda klawiaturę fizyczną, układ aplikacji może wymagać wykorzystania innego rozmiaru lub układu wyświetlacza. Jeśli orientacja urządzenia się zmieni – np. z pionowej na poziomą lub odwrotnie – układ może wymagać dostosowania do nowej orientacji.

Krok 1. Poznaj rotację urządzenia i wywołania zwrotne cyklu życia

  1. Skompiluj i uruchom aplikację, a następnie otwórz Logcat.
  2. Obróć urządzenie lub emulator do trybu poziomego. Możesz obracać emulator w lewo lub w prawo za pomocą przycisków obrotu albo klawiszy Control i strzałek (Command i klawiszy strzałek na Macu).
  3. Sprawdź dane wyjściowe w Logcat. Filtruj dane wyjściowe według MainActivity.
    Pamiętaj, że gdy urządzenie lub emulator obraca ekran, system wywołuje wszystkie wywołania zwrotne cyklu życia, aby zamknąć aktywność. Następnie, gdy aktywność zostanie ponownie utworzona, system wywoła wszystkie wywołania zwrotne cyklu życia, aby ją uruchomić.
  4. MainActivity zakomentuj całą metodę onSaveInstanceState().
  5. Skompiluj i uruchom ponownie aplikację. Kliknij babeczkę kilka razy i obróć urządzenie lub emulator. Gdy urządzenie zostanie obrócone, a aktywność zostanie zamknięta i ponownie utworzona, uruchomi się z wartościami domyślnymi.

    Gdy nastąpi zmiana konfiguracji, Android użyje tego samego pakietu stanu instancji, o którym była mowa w poprzednim zadaniu, aby zapisać i przywrócić stan aplikacji. Podobnie jak w przypadku zamknięcia procesu, użyj metody onSaveInstanceState(), aby umieścić dane aplikacji w pakiecie. Następnie przywróć dane w onCreate(), aby uniknąć utraty danych o stanie aktywności w przypadku obrócenia urządzenia.
  6. W pliku MainActivity odkomentuj metodę onSaveInstanceState(), uruchom aplikację, kliknij babeczkę i obróć aplikację lub urządzenie. Zauważ, że tym razem dane o deserze są zachowywane podczas rotacji aktywności.

Projekt Android Studio: DessertClickerFinal

Wskazówki dotyczące cyklu życia

  • Jeśli coś skonfigurujesz lub uruchomisz w wywołaniu zwrotnym cyklu życia, zatrzymaj lub usuń to w odpowiednim wywołaniu zwrotnym. Zatrzymanie elementu sprawia, że nie będzie on działać, gdy nie będzie już potrzebny. Jeśli na przykład ustawisz minutnik w onStart(), musisz go wstrzymać lub wyłączyć w onStop().
  • Używaj funkcji onCreate() tylko do inicjowania części aplikacji, które są uruchamiane jednorazowo przy pierwszym uruchomieniu aplikacji. Użyj onStart(), aby uruchomić części aplikacji, które działają zarówno po jej uruchomieniu, jak i za każdym razem, gdy aplikacja wraca na pierwszy plan.

Biblioteka cyklu życia

  • Użyj biblioteki cyklu życia Androida, aby przenieść kontrolę cyklu życia z aktywności lub fragmentu na rzeczywisty komponent, który musi być świadomy cyklu życia.
  • Właściciele cyklu życia to komponenty, które mają (a tym samym „posiadają”) cykle życia, w tym ActivityFragment. Właściciele cyklu życia implementują interfejs LifecycleOwner.
  • Obserwatorzy cyklu życia zwracają uwagę na bieżący stan cyklu życia i wykonują zadania, gdy cykl życia się zmienia. Obserwatorzy cyklu życia implementują interfejs LifecycleObserver.
  • Lifecycle zawierają rzeczywiste stany cyklu życia i wywołują zdarzenia, gdy cykl życia się zmienia.

Aby utworzyć klasę uwzględniającą cykl życia:

  • Zaimplementuj interfejs LifecycleObserver w klasach, które muszą być zgodne z cyklem życia.
  • Zainicjuj klasę obserwatora cyklu życia za pomocą obiektu cyklu życia z aktywności lub fragmentu.
  • W klasie obserwatora cyklu życia oznacz metody uwzględniające cykl życia stanem cyklu życia, który je interesuje.

    Na przykład adnotacja @OnLifecycleEvent(Lifecycle.Event.ON_START)wskazuje, że metoda obserwuje zdarzenie cyklu życia onStart.

Zamykanie procesów i zapisywanie stanu aktywności

  • Android reguluje działanie aplikacji w tle, aby aplikacja na pierwszym planie mogła działać bez problemów. Obejmuje to ograniczenie ilości przetwarzania, które aplikacje mogą wykonywać w tle, a czasami nawet zamknięcie całego procesu aplikacji.
  • Użytkownik nie może stwierdzić, czy system zamknął aplikację w tle. Aplikacja nadal będzie widoczna na ekranie ostatnich aplikacji i powinna zostać ponownie uruchomiona w stanie, w jakim użytkownik ją opuścił.
  • Android Debug Bridge (adb) to narzędzie wiersza poleceń, które umożliwia wysyłanie instrukcji do emulatorów i urządzeń podłączonych do komputera. Możesz użyć adb, aby symulować zamknięcie procesu w aplikacji.
  • Gdy Android zamyka proces aplikacji, metoda cyklu życia onDestroy() nie jest wywoływana. Aplikacja po prostu przestaje działać.

Zachowywanie stanu aktywności i fragmentu

  • Gdy aplikacja przechodzi w tło, tuż po wywołaniu funkcji onStop() dane aplikacji są zapisywane w pakiecie. Niektóre dane aplikacji, np. zawartość EditText, są automatycznie zapisywane.
  • Pakiet jest instancją Bundle, czyli zbiorem kluczy i wartości. Klucze są zawsze ciągami znaków.
  • Użyj wywołania zwrotnego onSaveInstanceState(), aby zapisać w pakiecie inne dane, które chcesz zachować, nawet jeśli aplikacja została automatycznie zamknięta. Aby umieścić dane w pakiecie, użyj metod pakietu, które zaczynają się od put, np. putInt().
  • Dane możesz odzyskać z pakietu za pomocą metody onRestoreInstanceState() lub częściej za pomocą metody onCreate(). Metoda onCreate() ma parametr savedInstanceState, który zawiera pakiet.
  • Jeśli zmienna savedInstanceState zawiera null, aktywność została rozpoczęta bez pakietu stanu i nie ma danych stanu do pobrania.
  • Aby pobrać dane z pakietu za pomocą klucza, użyj metod Bundle, które zaczynają się od get, np. getInt().

Zmiany konfiguracji

  • Zmiana konfiguracji następuje, gdy stan urządzenia zmienia się tak radykalnie, że najłatwiejszym sposobem rozwiązania tego problemu jest zamknięcie i ponowne utworzenie aktywności.
  • Najczęstszym przykładem zmiany konfiguracji jest obrócenie urządzenia z orientacji pionowej na poziomą lub z poziomej na pionową. Zmiana konfiguracji może też nastąpić, gdy zmieni się język urządzenia lub zostanie podłączona klawiatura sprzętowa.
  • Gdy nastąpi zmiana konfiguracji, Android wywoła wszystkie wywołania zwrotne zamykania cyklu życia aktywności. Następnie Android ponownie uruchamia aktywność od początku, wywołując wszystkie wywołania zwrotne uruchamiania cyklu życia.
  • Gdy Android zamyka aplikację z powodu zmiany konfiguracji, ponownie uruchamia aktywność z pakietem stanu dostępnym dla onCreate().
  • Podobnie jak w przypadku zamykania procesu, zapisz stan aplikacji w pakiecie w onSaveInstanceState().

Kurs Udacity:

Dokumentacja dla deweloperów aplikacji na Androida:

Inne:

W tej sekcji znajdziesz listę możliwych zadań domowych dla uczniów, którzy wykonują ten moduł w ramach kursu prowadzonego przez instruktora. Nauczyciel musi:

  • W razie potrzeby przypisz pracę domową.
  • Poinformuj uczniów, jak przesyłać projekty.
  • Oceń zadania domowe.

Instruktorzy mogą korzystać z tych sugestii w dowolnym zakresie i mogą zadawać inne zadania domowe, które uznają za odpowiednie.

Jeśli wykonujesz ten kurs samodzielnie, możesz użyć tych zadań domowych, aby sprawdzić swoją wiedzę.

Zmienianie aplikacji

Otwórz aplikację DiceRoller z lekcji 1. (Jeśli nie masz tej aplikacji, możesz ją pobrać tutaj). Skompiluj i uruchom aplikację. Zwróć uwagę, że jeśli obrócisz urządzenie, bieżąca wartość kostki zostanie utracona. Zaimplementuj onSaveInstanceState(), aby zachować tę wartość w pakiecie i przywrócić ją w onCreate().

Odpowiedz na te pytania

Pytanie 1

Twoja aplikacja zawiera symulację fizyczną, która wymaga dużej mocy obliczeniowej do wyświetlenia. Następnie użytkownik otrzyma połączenie telefoniczne. Które stwierdzenie jest prawdziwe?

  • Podczas rozmowy telefonicznej należy kontynuować obliczanie pozycji obiektów w symulacji fizycznej.
  • Podczas rozmowy telefonicznej należy zatrzymać obliczanie pozycji obiektów w symulacji fizycznej.

Pytanie 2

Którą metodę cyklu życia należy zastąpić, aby wstrzymać symulację, gdy aplikacja nie jest widoczna na ekranie?

  • onDestroy()
  • onStop()
  • onPause()
  • onSaveInstanceState()

Pytanie 3

Aby klasa była zgodna z cyklem życia za pomocą biblioteki cyklu życia Androida, który interfejs powinna implementować?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

Pytanie 4

W jakich okolicznościach metoda onCreate() w Twojej aktywności otrzymuje obiekt Bundle z danymi (czyli obiekt Bundle nie jest wartością null)? Może być więcej niż jedna odpowiedź.

  • Aktywność zostanie ponownie uruchomiona po obróceniu urządzenia.
  • Działanie jest rozpoczynane od zera.
  • Aktywność jest wznawiana po powrocie z tła.
  • Urządzenie uruchomi się ponownie.

Rozpocznij kolejną lekcję: 5.1. ViewModel i ViewModelFactory

Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.