Android Kotlin Fundamentals 04.2: złożone cykle życia

Te ćwiczenia są częścią kursu Android Kotlin Fundamentals. Skorzystaj z tego kursu, jeśli będziesz wykonywać kolejno kilka ćwiczeń z programowania. Wszystkie ćwiczenia z kursu są wymienione na stronie docelowej ćwiczeń z programowania na temat Kotlin.

Wprowadzenie

W trakcie ostatniego ćwiczenia z programowania poznaliśmy cykle życia Activity i Fragment oraz przeanalizowaliśmy metody, które są wywoływane po zmianie stanu cyklu życia w aktywnościach i fragmentach. W tym ćwiczeniu szczegółowo poznasz cykl życia aktywności. Poznasz też bibliotekę cykli życia Androida Jetpack, która pomoże Ci zarządzać zdarzeniami cyklu życia przy użyciu kodu, który jest lepiej zorganizowany i łatwiejszy w utrzymaniu.

Co warto wiedzieć

  • Czym jest aktywność i jak ją utworzyć w aplikacji
  • Podstawowe informacje o cyklach życia Activity i Fragment 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() i onStop(), aby wykonywać operacje w różnych momentach cyklu życia lub aktywności.

Czego się nauczysz:

  • Jak skonfigurować, uruchomić i zatrzymać części aplikacji w wywołaniach zwrotnych cyklu życia.
  • Jak za pomocą biblioteki cyklu życia Androida utworzyć obserwator cyklu życia i ułatwić zarządzanie nim oraz cyklem życia fragmentu.
  • Jak wyłączenie procesów Androida wpływa na dane w aplikacji oraz jak automatycznie zapisywać i przywracać dane po zamknięciu aplikacji.
  • Jak obrót urządzenia i inne zmiany w konfiguracji powodują zmiany stanów cyklu życia i wpływają na stan aplikacji.

Co chcesz zrobić

  • Zmodyfikuj aplikację DessertClicker, tak aby zawierała funkcję licznika czasu oraz uruchamiać i zatrzymywać minutnik w różnych momentach cyklu aktywności.
  • Zmodyfikuj aplikację, aby używać biblioteki cyklu życia Androida, i przekonwertuj klasę DessertTimer na obserwator cyklu życia.
  • Skonfiguruj i wykorzystaj Android Debug Bridge (adb) do symulowania procesu wyłączania aplikacji i wywołań zwrotnych cyklu życia.
  • Zaimplementuj metodę onSaveInstanceState(), by zachowywać dane aplikacji, które można utracić w przypadku nieoczekiwanego zamknięcia aplikacji. Dodaj kod, aby przywrócić te dane przy ponownym uruchomieniu aplikacji.

W ramach tego ćwiczenia rozwiniesz aplikację DessertClicker z poprzedniego ćwiczenia z programowania. Dodaj minutnik w tle, a potem przekonwertuj aplikację, aby użyć biblioteki cyklu życia Androida.

W poprzednim ćwiczeniu z programowania nauczyliśmy się obserwować aktywność i cykle życia fragmentów, nadpisując różne wywołania zwrotne cyklu życia, i logując się, gdy system wywoła te wywołania. W tym zadaniu poznasz bardziej złożony przykład zarządzania zadaniami cyklu życia w aplikacji DessertClicker. Korzystasz z minutnika, który co sekundę drukuje dziennik z liczbą aktywnych sekund.

Krok 1. Skonfiguruj DessertTimer

  1. Otwórz aplikację DessertClicker z ostatniego ćwiczenia z programowania. (Jeśli nie masz aplikacji, pobierz DessertClickerLogs tutaj).
  2. W widoku Project (Projekt) rozwiń plik ava > com.example.android.dessertclicker i otwórz DessertTimer.kt. Zwróć uwagę, że w tej chwili cały kod jest komentowany, więc nie działa jako część aplikacji.
  3. W oknie edytora zaznacz cały kod. Wybierz Kod > Komentarz z komentarzem wiersza lub naciśnij Control+/ (Command+/ na Macu). To polecenie usuwa komentarz do całego kodu w pliku. (Android Studio może wyświetlać nierozwiązane błędy plików referencyjnych do momentu ponownego utworzenia aplikacji).
  4. Klasa DessertTimer obejmuje startTimer() i stopTimer(), które uruchamiają i zatrzymują minutnik. Gdy startTimer() działa, licznik czasu wyświetla komunikat dziennika co sekundę z łączną liczbą sekund, które upłynął. Z kolei metoda stopTimer() zatrzymuje działanie licznika czasu oraz instrukcji logu.
  1. Otwórz aplikację MainActivity.kt. U góry klasy, tuż pod zmienną dessertsSold, dodaj zmienną minutnika:
private lateinit var dessertTimer : DessertTimer;
  1. Przewiń w dół do onCreate() i utwórz nowy obiekt DessertTimer, tuż po wywołaniu adresu setOnClickListener():
dessertTimer = DessertTimer()


Teraz, gdy masz już licznik czasu na deser, zastanów się, gdzie zacząć i zatrzymać stoper, aby uruchomił się tylko na ekranie. Przyjrzyjmy się kilku możliwościom w kolejnych krokach.

Krok 2. Włącz i zatrzymaj minutnik

Metoda onStart() jest wywoływana tuż przed pojawieniem się aktywności. Metoda onStop() jest wywoływana, gdy aktywność przestanie być widoczna. Wydaje się nam, że te wywołania zwrotne mogą być przydatne w takim przypadku.

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

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

   Timber.i("onStop Called")
}
  1. Skompilować i uruchomić aplikację. W Android Studio kliknij panel Logcat. W polu wyszukiwania Logcat wpisz dessertclicker, który będzie filtrowany według klas MainActivity i DessertTimer. Pamiętaj, że gdy uruchomi się aplikacja, licznik zacznie działać od razu.
  2. Kliknij przycisk Wstecz i sprawdź, czy stoper został zatrzymany. Minutnik zostanie zatrzymany, ponieważ zarówno aktywność, jak i wyzwalacz, które steruje, zostały zniszczone.
  3. Użyj ekranu Ostatnie, by wrócić do aplikacji. Zapisz w Logcat, że licznik czasu zaczyna się od 0.
  4. Kliknij przycisk Udostępnij. Zauważ w Logcat, że licznik czasu nadal działa.

  5. Kliknij przycisk Strona główna. Komunikat w Logcat, że licznik czasu nie działa.
  6. Użyj ekranu Ostatnie, by wrócić do aplikacji. Zapisz w Logcat, że licznik uruchamia się z powrotem.
  7. W metodzie MainActivity w metodzie onStop() dodaj komentarz do wywołania stopTimer(). Komentowanie stopTimer() pokazuje sytuację, w której rozpoczynasz operację w onStart(), ale pamiętaj, aby zatrzymać ją ponownie w onStop().
  8. Skompilować i uruchomić aplikację, a następnie kliknąć przycisk ekranu głównego po uruchomieniu minutnika. Mimo że aplikacja działa w tle, licznik czasu działa i ciągle korzysta z zasobów systemowych. Uruchamianie stopera powoduje wyciek pamięci i najprawdopodobniej nie działa zgodnie z oczekiwaniami.

    Ogólny schemat polega na tym, że gdy konfigurujesz lub uruchamiasz wywołanie zwrotne, zatrzymujesz lub usuwasz je w odpowiednim oddzwanianiu. Dzięki temu unikniesz sytuacji, w której byłoby to niemożliwe.
  1. Odznacz wiersz w miejscu onStop(), na którym zatrzymujesz minutnik.
  2. Wytnij i wklej połączenie startTimer() z numeru onStart() do aplikacji onCreate(). Ta zmiana przedstawia przypadek inicjowania i uruchamiania zasobu w onCreate(), a nie uruchamiania onCreate() i uruchamiania go za pomocą onStart().
  3. Skompiluj i uruchom aplikację. Zauważ, że minutnik działa zgodnie z oczekiwaniami.
  4. Kliknij Ekran główny, aby zatrzymać aplikację. Minutnik przestanie działać zgodnie z oczekiwaniami.
  5. Użyj ekranu Ostatnie, by wrócić do aplikacji. Pamiętaj, że w tym przypadku stoper nie zaczyna się od nowa, ponieważ onCreate() jest wywoływany tylko podczas uruchamiania aplikacji, a nie jest wywoływany, gdy aplikacja wraca na pierwszy plan.

Ważne uwagi:

  • Gdy skonfigurujesz zasób w wywołaniu zwrotnym cyklu życia, musisz też go zniszczyć.
  • Skonfiguruj i zdemontuj, używając odpowiednich metod.
  • Jeśli skonfigurujesz coś w usłudze onStart(), zatrzymaj go lub ponownie przekręć w onStop().

W aplikacji DessertClicker łatwo zauważyć, że jeśli licznik czasu został uruchomiony w onStart(), musisz go zatrzymać w onStop(). Ma on tylko 1 minutnik, więc jego zatrzymanie nie jest trudne do zapamiętania.

W bardziej złożonej aplikacji na Androida możesz skonfigurować wiele elementów w onStart() i onCreate(), a następnie zniszczyć je w onStop() lub onDestroy(). Możesz mieć np. animacje, muzykę, czujniki lub minutniki, które musisz skonfigurować i zdemontować, a także uruchamiać i zatrzymywać. Jeśli o tym zapomnisz, będziesz mieć problemy i bóle głowy.

Uprości to zadanie, korzystając z biblioteki cyklu życia, która jest częścią Androida Jetpack. Jest ona przydatna zwłaszcza w sytuacjach, gdy musisz śledzić wiele przemieszczających się części, a niektóre z nich znajdują się w różnych stanach cyklu życia. Biblioteka odwraca przebieg cyklu życia: zazwyczaj aktywność lub fragment informuje komponent (np. DessertTimer), co zrobić, gdy występuje wywołanie zwrotne cyklu życia. Gdy jednak korzystasz z biblioteki cyklu życia, komponent sam monitoruje zmiany cyklu życia, a następnie robi to, co następuje.

Biblioteka cyklu życia składa się z 3 głównych elementów:

  • Właściciele cyklu życia, czyli komponenty, które mają (i w konsekwencji) cykl życia. Activity i Fragment są właścicielami cyklu życia. Właściciele cyklu życia stosują interfejs LifecycleOwner.
  • Klasa Lifecycle, która zawiera rzeczywisty stan właściciela cyklu życia i uruchamia zdarzenia w momencie zmiany cyklu życia.
  • Serwery cyklu życia, które śledzą stan cyklu życia i wykonują zadania w razie jego zmiany. Obserwatorzy cyklu życia stosują interfejs LifecycleObserver.

W tym zadaniu przekształcasz aplikację DessertClicker, aby korzystać z biblioteki cyklu życia Androida, oraz dowiesz się, w jaki sposób biblioteka ułatwia zarządzanie aktywnością w Androidzie i cyklami życia fragmentu.

Krok 1. Przekształć DessertTimer w serwer LifecycleObserver

Kluczowym elementem biblioteki cyklu życia jest koncepcja obserwowania cyklu życia. Obserwacja umożliwia klasom (takim jak DessertTimer) uzyskiwanie informacji o aktywności lub cyklu życia fragmentu i uruchamianie ich w odpowiedzi na zmiany w tych stanach cyklu życia. Korzystając z obserwatora cyklu życia, możesz zrezygnować z uruchamiania i zatrzymywania obiektów w metodach aktywności i fragmentów.

  1. Otwórz klasę DesertTimer.kt.
  2. Zmień podpis klasy DessertTimer w ten sposób:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

Ta nowa definicja klasy ma dwie funkcje:

  • Konstruktor przyjmuje obiekt Lifecycle, który jest cyklem życia zaobserwowanym przez minutnik.
  • Definicja klasy implementuje interfejs LifecycleObserver.
  1. Pod zmienną runnable dodaj blok init do definicji klasy. W bloku init użyj metody addObserver(), by połączyć obiekt cyklu życia przekazany od właściciela (aktywności) z tą klasą (obserwatorem).
 init {
   lifecycle.addObserver(this)
}
  1. Dodaj do elementu startTimer() adnotacje @OnLifecycleEvent annotation i użyj zdarzenia cyklu życia ON_START. Wszystkie zdarzenia cyklu życia, które może obserwować obserwator cyklu życia, należą do klasy Lifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. Wykonaj to samo co stopTimer(), używając zdarzenia ON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

Krok 2. Zmodyfikuj główną aktywność

Klasa MainActivity jest już właścicielem cyklu życia dzięki dziedziczeniu, ponieważ klasa nadrzędna FragmentActivity implementuje LifecycleOwner. W związku z tym nie jest wymagane żadne działanie, które należy skonfigurować, aby aktywność zależała od cyklu życia. Wystarczy, że przekażesz obiekt cyklu życia aktywności do konstruktora DessertTimer.

  1. Otwórz aplikację MainActivity. W metodzie onCreate() zmień inicjację elementu DessertTimer, tak aby zawierał this.lifecycle:
dessertTimer = DessertTimer(this.lifecycle)

Właściwość lifecycle aktywności zawiera obiekt Lifecycle, do którego należy ta aktywność.

  1. Usuń połączenie z numerem startTimer() w elemencie onCreate() i połączenie z numerem stopTimer() w aplikacji onStop(). Nie musisz już informować użytkownika DessertTimer, co ma zrobić w przypadku tej aktywności, ponieważ DessertTimer rejestruje teraz sam cykl życia i jest automatycznie powiadamiany o zmianie jego stanu. Teraz w tych wywołaniach zwrotnych rejestrujesz wiadomości.
  2. Skompiluj i uruchom aplikację, a następnie otwórz aplikację Logcat. Minutnik zaczął działać zgodnie z oczekiwaniami.
  3. Kliknij przycisk ekranu głównego, aby umieścić aplikację w tle. Minutnik przestał działać.

Co dzieje się z aplikacją i jej danymi, gdy Android wyłącza aplikację, gdy jest w tle? Trzeba zrozumieć ten skomplikowany przypadek.

Gdy aplikacja działa w tle, nie jest niszczona. Zatrzymaj się i poczekaj, aż użytkownik wróci na Twoją stronę. Jedną z głównych kwestii jest to, że aktywność na pierwszym planie działa sprawnie. Jeśli na przykład użytkownik korzysta z aplikacji GPS, aby ułatwić sobie zmierzenie się w autobusie, należy szybko renderować tę aplikację i wyświetlać wskazówki dojazdu. Mniej ważne jest, aby zachować aplikację DessertClicker, do której użytkownik nie wszedł jeszcze przez kilka dni, i działał płynnie w tle.

Android reguluje aplikacje działające w tle, tak aby pierwsza aplikacja działała bez problemów. Na przykład Android ogranicza ilość przetwarzania, które mogą wykonywać aplikacje działające w tle.

Czasami Android wyłącza nawet cały proces aplikacji, który obejmuje wszystkie powiązane z nią czynności. Android wyłącza się, gdy system jest stresowany i jest zagrożony opóźnieniami, więc nie są wywoływane żadne dodatkowe wywołania zwrotne ani kod. Ten proces jest po prostu wyłączany w tle w tle. Wygląda jednak na to, że aplikacja nie jest zamknięta. Gdy użytkownik wróci do aplikacji, w której system operacyjny Android został wyłączony, Android ponownie uruchomi tę aplikację.

W tym zadaniu symulujesz wyłączenie procesu Androida i sprawdzasz, co dzieje się z aplikacją po ponownym uruchomieniu.

Krok 1. Użyj symulacji adb, aby zasymulować zakończenie 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 korzystasz z adb, aby zamknąć proces aplikacji i sprawdzić, co się stanie, gdy ją wyłączymy.

  1. Skompiluj i uruchom aplikację. Kilka razy kliknij babeczkę.
  2. Naciśnij przycisk ekranu głównego, aby uruchomić aplikację w tle. Twoja aplikacja została zatrzymana i zostanie zamknięta, jeśli Android potrzebuje zasobów, z których korzysta.
  3. W Android Studio kliknij kartę Terminal, aby otworzyć terminal wiersza poleceń.
  4. Wpisz adb i naciśnij Enter.

    Jeśli zobaczysz wiele danych wyjściowych, które zaczynają się od Android Debug Bridge version X.XX.X i kończą 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 w ścieżce wykonywania jest dostępne polecenie adb. Instrukcje znajdziesz w sekcji „Dodawanie adb do ścieżki wykonania” w rozdziale Narzędzia.
  5. Skopiuj ten komentarz i wklej go w wierszu poleceń, a następnie naciśnij Enter:
adb shell am kill com.example.android.dessertclicker

Polecenie to informuje wszystkie podłączone urządzenia lub emulatory o konieczności zakończenia procesu przy użyciu nazwy pakietu dessertclicker, ale tylko wtedy, gdy aplikacja działa w tle. Ponieważ aplikacja działała w tle, na ekranie urządzenia lub w emulatorze nie pojawi się żaden komunikat wskazujący, że proces został zatrzymany. W Android Studio kliknij kartę Uruchom, aby wyświetlić komunikat „Aplikacja została zakończona”." Kliknij kartę Logcat, aby zobaczyć, że wywołanie zwrotne onDestroy() nie zostało uruchomione – Twoja aktywność właśnie się zakończyła.

  1. Aby wrócić do aplikacji, użyj ekranu Ostatnie. Została ona wyświetlona w ostatnich wynikach niezależnie od tego, czy została umieszczona w tle czy została całkowicie zatrzymana. Gdy wrócisz do aplikacji, korzystając z ekranu Ostatnie, aktywność zostanie ponownie uruchomiona. Aktywność obejmuje cały zestaw wywołań zwrotnych cyklu życia startupu, w tym onCreate().
  2. Zauważ, że po ponownym uruchomieniu aplikacja resetuje Twój wynik i liczbę domyślnych deserów (0), a także liczbę sprzedanych deserów. Jeśli Android wyłączy Twoją aplikację, dlaczego nie zapisał Twojego stanu?

    Gdy system operacyjny ponownie uruchomi aplikację, Android dołoży wszelkich starań, aby ją zresetować do stanu sprzed wprowadzenia zmian. Android obierze stan niektórych widoków i zapisze je w pakiecie za każdym razem, gdy opuścisz tę aktywność. Dane, które są automatycznie zapisywane, to na przykład tekst w elemencie Edytuj tekst (o ile mają identyfikator w układzie) i tylny stos Twojej aktywności.

    Czasem jednak system operacyjny Android nie zna wszystkich danych. Jeśli na przykład masz zmienną niestandardową taką jak revenue w aplikacji DessertClicker, 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 funkcji onSaveInstanceState(), aby zapisać dane pakietu

Metoda onSaveInstanceState() to wywołanie zwrotne, które pozwala zapisać wszelkie dane, których możesz potrzebować, jeśli system operacyjny Android zniszczy Twoją aplikację. Na diagramie zwrotnym cyklu życia parametr onSaveInstanceState() jest wywoływany po zatrzymaniu aktywności. Jest ona wywoływana za każdym razem, gdy aplikacja działa w tle.

Wywołanie onSaveInstanceState() jest środkiem bezpieczeństwa, który daje możliwość zapisania małej ilości informacji w grupie, gdy aktywność wychodzi na pierwszym planie. System zapisuje teraz te dane, ponieważ jeśli czekał na wyłączenie aplikacji, system operacyjny może być pod presją zasobów. Za każdym razem, gdy zajdzie taka potrzeba, zyskasz pewność, że dane w pakiecie będzie można przywrócić.

  1. W 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 Strona główna, aby umieścić ją w tle. Zwróć uwagę, że wywołanie zwrotne onSaveInstanceState() ma miejsce zaraz po onPause() i 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"

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

  1. Przewiń w dół do onSaveInstanceState() i zobacz, że parametr outState ma typ Bundle.

    Pakiet to zbiór par klucz-wartość, w których klucze są zawsze ciągami. W pakiecie możesz umieścić wartości podstawowe, takie jak int i boolean.
    Ponieważ system przechowuje ten pakiet w pamięci RAM, najlepiej jest zadbać o to, aby dane w pakiecie były małe. Ten pakiet jest też ograniczony, ale rozmiar różni się w zależności od urządzenia. Zazwyczaj należy przechowywać mniej niż 100 tys. plików. W przeciwnym razie wystąpi błąd awarii aplikacji TransactionTooLargeException.
  2. W polu onSaveInstanceState() umieść wartość revenue (liczbę całkowitą) w grupie, korzystając z metody putInt():
outState.putInt(KEY_REVENUE, revenue)

Metoda putInt() (i podobne metody z klasy Bundle, takiej jak putFloat() i putString(), przyjmuje 2 argumenty: ciąg klucza (stała KEY_REVENUE) i faktyczną wartość do zapisania.

  1. Powtórz te same czynności, podając liczbę sprzedawanych deserów i stan licznika:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

Krok 3. Użyj funkcji onCreate() do przywrócenia danych pakietu

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

Zwróć uwagę, że tag onCreate() otrzymuje Bundle przy każdym wywołaniu. Gdy aktywność zostanie ponownie uruchomiona z powodu wyłączenia procesu, zapisany pakiet zostanie przekazany do dostawcy onCreate(). Jeśli Twoja aktywność zaczynała od początku, ten pakiet w grupie onCreate() to null. Zatem jeśli pakiet nie pochodzi z null, znasz aktywność, która pochodzi od znanego punktu.

  1. Po konfiguracji DessertTimer dodaj ten kod do aplikacji onCreate():
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

Test null określa, czy w pakiecie są dane, czy jest to pakiet null, który informuje o tym, czy aplikacja została uruchomiona od nowa, czy została ponownie utworzona po wyłączeniu. Ten test to typowy wzorzec przywracania danych z pakietu.

Zwróć uwagę, że użyty tu klucz (KEY_REVENUE) jest tym samym, którego użyto w narzędziu putInt(). Aby mieć pewność, że używasz tego samego klucza za każdym razem, warto zdefiniować je jako stałe. Korzystasz z programu getInt(), by pobierać dane z pakietu tak samo jak w przypadku usługi putInt(). Metoda getInt() przyjmuje dwa argumenty:

  • Ciąg, który pełni funkcję klucza, np. "key_revenue" jako wartość przychodów.
  • Wartość domyślna w przypadku braku wartości dla tego klucza w pakiecie.

Liczba całkowita uzyskana w pakiecie jest przypisywana do zmiennej revenue, która jest używana w interfejsie.

  1. Dodaj metody getInt(), które pozwolą przywrócić liczbę sprzedanych deserów i wartość licznika:
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 co najmniej 5 babeczek, aż zmieni się w pączek. Kliknij przycisk ekranu głównego, aby umieścić aplikację w tle.
  2. Na karcie Terminal w Android Studio uruchom adb, aby wyłączyć proces aplikacji.
adb shell am kill com.example.android.dessertclicker
  1. Użyj ekranu Ostatnie , aby wrócić do aplikacji. Zwróć uwagę, że tym razem aplikacja powraca z prawidłowymi wartościami przychodów i sprzedanych deserów z pakietu. Zwróć jednak uwagę, że deser wrócił do babeczki. Musisz jeszcze wykonać jedną rzecz, by upewnić się, że aplikacja powróci w taki sposób, w jaki została wyłączona.
  2. W MainActivity sprawdź metodę showCurrentDessert(). Zauważ, że ta metoda umożliwia wybór obrazu deseru, który zostanie wyświetlony w aktywności na podstawie bieżącej liczby sprzedanych deserów i listy deserów w zmiennej allDesserts.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

Przy wyborze metody odpowiednie jest podanie liczby deserów. Dlatego nie musisz nic robić, aby zapisać odniesienie do obrazu w pakiecie w grupie onSaveInstanceState(). W tym pakiecie przechowujesz już liczbę sprzedanych deserów.

  1. W bloku onCreate(), który przywraca stan z pakietu, wywołaj 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 zamknąć proces, użyj aplikacji adb. Użyj ekranu Ostatnie , aby wrócić do aplikacji. Zwróć uwagę, że wartości dotyczące deserów, łączne przychody i obraz deseru zostały przywrócone.

Jest ostatni przypadek zarządzania aktywnością i cyklem życia fragmentu, który jest najważniejszy: jak zmiany konfiguracji wpływają na cykl życia aktywności i fragmentów.

Zmiana konfiguracji ma miejsce wtedy, gdy stan urządzenia zmienia się tak bardzo, że najprostszym sposobem na wprowadzenie zmiany w systemie jest pełne wyłączenie i odtworzenie aktywności. Jeśli na przykład użytkownik zmieni język na urządzeniu, może zajść konieczność zmiany całego układu, by dostosować go do różnych tras tekstowych. Jeśli użytkownik podłączy urządzenie do stacji dokującej lub doda fizyczną klawiaturę, konieczne może być wykorzystanie innego układu ekranu lub innego układu ekranu. Jeśli orientacja urządzenia zmieni się z pionowej do poziomej lub odwrotnie, układ może wymagać zmiany orientacji.

Krok 1. Sprawdź rotację urządzeń i wywołania zwrotne cyklu życia

  1. Skompiluj i uruchom aplikację, a następnie otwórz aplikację Logcat.
  2. Obróć urządzenie lub emulator do trybu poziomego. Emulator możesz obracać w lewo lub w prawo, używając przycisków obrotu lub klawiszy Control i strzałek (Command i klawisze strzałek na Macu).
  3. Sprawdź dane wyjściowe w Logcat. Przefiltruj dane wyjściowe na MainActivity.
    Zwróć uwagę, że gdy urządzenie lub emulator obróci ekran, system wywoła wszystkie wywołania zwrotne cyklu życia, by wyłączyć aktywność. Po ponownym odtworzeniu aktywności system wywołuje wszystkie wywołania zwrotne cyklu życia, by rozpocząć działanie.
  4. MainActivity – dodaj komentarz do całej metody onSaveInstanceState().
  5. Skompiluj aplikację i uruchom ją ponownie. Kliknij kilka razy babeczkę i obróć urządzenie lub emulator. Tym razem, gdy urządzenie zostanie obrócone, a aktywność zostanie zatrzymana i zostanie utworzona ponownie, aktywność zaczyna się od domyślnych wartości.

    Po zmianie konfiguracji Android zapisuje zmiany w stanie aplikacji za pomocą tego samego pakietu, który znasz z poprzedniego zadania. Podobnie jak w przypadku wyłączenia procesu, użyj onSaveInstanceState(), by umieścić dane aplikacji w pakiecie. Następnie przywróć dane w onCreate(), aby uniknąć utraty danych o stanie aktywności, gdy urządzenie jest obrócone.
  6. W MainActivity usuń komentarz z metodą onSaveInstanceState(), uruchom aplikację, kliknij babeczka i obróć aplikację lub urządzenie. Zwróć uwagę, że tym razem dane deserów są zachowywane w rotacji aktywności.

Projekt na Android Studio: DessertClickerFinal

Wskazówki dotyczące cyklu życia

  • Jeśli skonfigurujesz lub uruchomisz wywołanie zwrotne wywołania cyklu życia, zatrzymaj je lub usuń z tego wywołania zwrotnego. Gdy zatrzymasz ten proces, upewnisz się, że nie będzie on działał, gdy nie będzie już potrzebny. Jeśli na przykład masz ustawiony minutnik w onStart(), musisz go wstrzymać lub zatrzymać w onStop().
  • Używaj tagu onCreate() tylko raz, aby inicjować te części aplikacji, które działają jeden raz przy pierwszym uruchomieniu. Użyj polecenia onStart(), aby uruchamiać części aplikacji, które są uruchamiane zarówno po uruchomieniu aplikacji, jak i po wyświetleniu jej na pierwszym planie.

Biblioteka cyklu życia

  • Użyj biblioteki cyklu życia Androida, aby zmienić kontrolę cyklu życia z czynności lub fragmentu na komponent, który musi być zależny od cyklu życia.
  • Właściciele cyklu życia to komponenty, które mają (i w konsekwencji) cykle życia, w tym Activity i Fragment. Właściciele cyklu życia stosują interfejs LifecycleOwner.
  • Serwery cyklu życia zwracają uwagę na aktualny stan cyklu życia i wykonują zadania w przypadku zmiany cyklu życia. Obserwatorzy cyklu życia stosują interfejs LifecycleObserver.
  • Obiekty Lifecycle zawierają rzeczywiste stany cyklu życia i wywołują zdarzenia, gdy zmieni się cykl życia.

Aby utworzyć klasę zależną od cyklu życia:

  • Zaimplementuj interfejs LifecycleObserver w klasach, które mają być dostosowane do cyklu życia.
  • Zainicjuj klasę obserwatora cyklu życia za pomocą obiektu cyklu życia z aktywności lub fragmentu.
  • W klasie odbiorcy cyklu życia dodaj adnotacje zależne od cyklu życia ze stanem cyklu życia, który ich interesuje.

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

Procesy wyłączają się i zapisują stan aktywności

  • Android reguluje aplikacje działające w tle, tak aby pierwsza aplikacja działała bez problemów. Regulacja obejmuje ograniczenie przetwarzania, które mogą wykonać aplikacje działające w tle, a czasem nawet wyłączenie całego procesu.
  • Użytkownik nie wie, czy system wyłączył aplikację w tle. Aplikacja nadal jest wyświetlana na ekranie Ostatnie i powinna zostać uruchomiona ponownie w stanie, w jakim została usunięta przez użytkownika.
  • 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. Za pomocą funkcji adb możesz symulować wyłączenie procesu w aplikacji.
  • Gdy Android wyłącza proces aplikacji, metoda cyklu życia onDestroy() nie jest wywoływana. Aplikacja przestaje działać.

Zachowanie aktywności i stanu fragmentu

  • Gdy aplikacja uruchomi się w tle, zaraz po wywołaniu funkcji onStop() dane aplikacji są zapisywane w pakiecie. Niektóre dane aplikacji, takie jak zawartość EditText, są zapisywane automatycznie.
  • Pakiet to wystąpienie Bundle, które jest zbiorem kluczy i wartości. Klucze są zawsze ciągami.
  • Wywołanie zwrotne onSaveInstanceState() pozwala zapisać inne dane w pakiecie, który chcesz zachować, nawet jeśli aplikacja została automatycznie zamknięta. Aby dodać dane do pakietu, użyj metod, które zaczynają się od put, np. putInt().
  • Możesz pobrać dane z grupy za pomocą metody onRestoreInstanceState() lub częściej w ramach metody onCreate(). Metoda onCreate() zawiera 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 zaczynających się od get, takich jak getInt().

Zmiany konfiguracji

  • Zmiana konfiguracji ma miejsce, gdy stan urządzenia zmienia się tak bardzo, że najprostszym sposobem na wprowadzenie zmiany w systemie jest wyłączenie i odtworzenie aktywności.
  • Najczęstszym przykładem zmian konfiguracji jest obrócenie urządzenia z pozycji pionowej do poziomej lub pionowej. Zmiana konfiguracji może też wystąpić po zmianie języka urządzenia lub podłączeniu klawiatury sprzętowej.
  • W razie zmiany konfiguracji Android wywołuje wywołania zwrotne aktywności cyklu życia. Następnie Android ponownie uruchamia aktywność od zera i uruchamia wszystkie wywołania zwrotne uruchamiania cyklu życia.
  • Gdy Android wyłącza aplikację z powodu zmiany konfiguracji, ponownie uruchamia aktywność przy użyciu pakietu stanu dostępnego dla aplikacji onCreate().
  • Tak jak w przypadku wyłączenia procesu, zapisz stan aplikacji w pakiecie onSaveInstanceState().

Kurs Udacity:

Dokumentacja dla programistów Androida:

Inne:

Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:

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

Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.

Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.

Zmienianie aplikacji

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

Odpowiedz na te pytania

Pytanie 1

Twoja aplikacja zawiera symulację fizyki, która wymaga wykonania dużej mocy obliczeniowej. Użytkownik odbiera wtedy połączenie telefoniczne. Które stwierdzenie jest prawdziwe?

  • Podczas połączenia telefonicznego należy kontynuować przetwarzanie pozycji obiektów w symulacji fizyki.
  • W trakcie połączenia telefonicznego należy zakończyć przetwarzanie pozycji obiektów w symulacji fizyki.

Pytanie 2

Którą metodę cyklu życia musisz zastąpić, by wstrzymać symulację, gdy aplikacji nie ma na ekranie?

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

Pytanie 3

Który interfejs powinien wdrożyć klasa, aby była dostępna w bibliotece cyklu życia Androida?

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

Pytanie 4

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

  • Aktywność zostanie wznowiona po obróceniu urządzenia.
  • Aktywność zaczyna się od zera.
  • Po wznowieniu działania w tle działanie zostanie wznowione.
  • Urządzenie zostanie zrestartowane.

Rozpocznij następną lekcję: 5.1: ViewModel i ViewModelFactory

Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.