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 Activity i Fragment 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
ActivityiFragmentoraz wywołania zwrotne, które są wywoływane, gdy aktywność przechodzi między stanami. - Jak zastąpić metody wywołania zwrotnego cyklu życia
onCreate()ionStop(), 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ę
DessertTimerw 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
- Otwórz aplikację DessertClicker z ostatnich zajęć. (Jeśli nie masz aplikacji, możesz pobrać pliki DessertClickerLogs tutaj).
- 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. - 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). - Zwróć uwagę, że klasa
DessertTimerzawiera elementystartTimer()istopTimer(), które uruchamiają i zatrzymują stoper. Gdy programstartTimer()jest uruchomiony, licznik czasu co sekundę wyświetla komunikat dziennika z całkowitą liczbą sekund, przez które był uruchomiony. MetodastopTimer()zatrzymuje minutnik i instrukcje logowania.
- Otwórz pokój
MainActivity.kt. U góry klasy, tuż pod zmiennądessertsSold, dodaj zmienną timera:
private lateinit var dessertTimer : DessertTimer;- Przewiń w dół do sekcji
onCreate()i utwórz nowy obiektDessertTimertuż po wywołaniu funkcjisetOnClickListener():
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.
- W klasie
MainActivityuruchom minutnik w wywołaniu zwrotnymonStart():
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}- Zatrzymaj minutnik za
onStop():
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}- Skompiluj i uruchom aplikację. W Android Studio kliknij panel Logcat. W polu wyszukiwania Logcat wpisz
dessertclicker, aby filtrować według klasMainActivityiDessertTimer. Zauważ, że po uruchomieniu aplikacji licznik czasu również zaczyna działać natychmiast.
- 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.
- Użyj ekranu ostatnich aplikacji, aby wrócić do aplikacji. Zwróć uwagę, że w narzędziu Logcat licznik czasu uruchamia się ponownie od 0.
- Kliknij przycisk Udostępnij. Zwróć uwagę, że w Logcat minutnik nadal działa.

- Kliknij przycisk ekranu głównego. Zwróć uwagę, że w Logcat minutnik przestaje działać.
- 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.
- W pliku
MainActivityw metodzieonStop()zmień w komentarz wywołanie funkcjistopTimer(). ZakomentowaniestopTimer()pokazuje sytuację, w której rozpoczynasz operację wonStart(), ale zapominasz ją zatrzymać wonStop(). - 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.
- Usuń znacznik komentarza z wiersza w pliku
onStop(), w którym zatrzymujesz minutnik. - Wytnij i wklej połączenie
startTimer()zonStart()doonCreate(). Ta zmiana pokazuje przypadek, w którym zasób jest inicjowany i uruchamiany wonCreate(), zamiast inicjować go wonCreate()i uruchamiać wonStart(). - Skompiluj i uruchom aplikację. Zegar zacznie odliczać czas zgodnie z oczekiwaniami.
- Kliknij Home, aby zatrzymać aplikację. Minutnik zatrzyma się zgodnie z oczekiwaniami.
- 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ąć wonStop().
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
ActivityiFragmentsą właścicielami cyklu życia. Właściciele cyklu życia implementują interfejsLifecycleOwner. - 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.
- Otwórz zajęcia
DesertTimer.kt. - 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.
- Pod zmienną
runnabledodaj blokinitdo definicji klasy. W blokuinitużyj metodyaddObserver(), aby połączyć obiekt cyklu życia przekazany od właściciela (aktywności) z tą klasą (obserwatorem).
init {
lifecycle.addObserver(this)
}- Dodaj adnotację
startTimer()za pomocą@OnLifecycleEvent annotationi użyj zdarzenia cyklu życiaON_START. Wszystkie zdarzenia cyklu życia, które może obserwować obserwator cyklu życia, znajdują się w klasieLifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {- Zrób to samo w przypadku
stopTimer(), używając zdarzeniaON_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.
- Otwórz pokój
MainActivity. W metodzieonCreate()zmodyfikuj inicjowanieDessertTimer, 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ść.
- Usuń połączenie z numerem
startTimer()wonCreate()i połączenie z numeremstopTimer()wonStop(). Nie musisz już informowaćDessertTimero tym, co ma robić w aktywności, ponieważDessertTimerobserwuje teraz cykl życia i jest automatycznie powiadamiany o zmianach stanu cyklu życia. W tych wywołaniach zwrotnych wystarczy teraz zalogować wiadomość. - Skompiluj i uruchom aplikację, a następnie otwórz Logcat. Zauważ, że minutnik zaczął działać zgodnie z oczekiwaniami.

- 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ę.
- Skompiluj i uruchom aplikację. Kliknij babeczkę kilka razy.
- 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.
- W Android Studio kliknij kartę Terminal, aby otworzyć terminal wiersza poleceń.

- Wpisz
adbi naciśnij Return.
Jeśli zobaczysz wiele wyników zaczynających się odAndroid Debug Bridge version X.XX.Xi kończących się natags to be used by logcat (see logcat —help), wszystko jest w porządku. Jeśli zamiast tego widziszadb: command not found, sprawdź, czy polecenieadbjest dostępne w ścieżce wykonywania. Instrukcje znajdziesz w sekcji „Dodawanie adb do ścieżki wykonywania” w rozdziale Narzędzia. - Skopiuj ten komentarz i wklej go w wierszu poleceń, a następnie naciśnij Return:
adb shell am kill com.example.android.dessertclickerTo 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.
- 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(). - 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.
- W
MainActivityzastąp wywołanie zwrotneonSaveInstanceState()i dodaj instrukcję logowaniaTimber.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.i("onSaveInstanceState Called")
}- 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ż poonPause()ionStop():
- 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.
- Przewiń w dół do
onSaveInstanceState()i zwróć uwagę na parametroutState, który jest typuBundle.
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ściintiboolean.
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łęduTransactionTooLargeException. - W
onSaveInstanceState()umieść wartośćrevenue(liczbę całkowitą) w pakiecie za pomocą metodyputInt():
outState.putInt(KEY_REVENUE, revenue)Metoda putInt() (i podobne metody z klasy Bundle, takie jak putFloat() i putString()) przyjmuje 2 argumenty: ciąg znaków klucza (stałą KEY_REVENUE) i rzeczywistą wartość do zapisania.
- 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
- 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.
- Dodaj ten kod do pliku
onCreate()po skonfigurowaniuDessertTimer:
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.
- 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)
}- Skompiluj i uruchom aplikację. Naciśnij babeczkę co najmniej 5 razy, aż zmieni się w donuta. Kliknij Home, aby przenieść aplikację w tle.
- Na karcie Terminal w Android Studio uruchom polecenie
adb, aby zamknąć proces aplikacji.
adb shell am kill com.example.android.dessertclicker- 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ść.
- W
MainActivitysprawdź 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 zmiennejallDesserts.
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.
- 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()
}- 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
- Skompiluj i uruchom aplikację, a następnie otwórz Logcat.
- Obróć urządzenie lub emulator do trybu poziomego. Możesz obracać emulator w lewo lub w prawo za pomocą przycisków obrotu albo klawiszy
Controli strzałek (Commandi klawiszy strzałek na Macu).
- 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ć. - W
MainActivityzakomentuj całą metodęonSaveInstanceState(). - 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 metodyonSaveInstanceState(), aby umieścić dane aplikacji w pakiecie. Następnie przywróć dane wonCreate(), aby uniknąć utraty danych o stanie aktywności w przypadku obrócenia urządzenia. - W pliku
MainActivityodkomentuj 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ć wonStop(). - Używaj funkcji
onCreate()tylko do inicjowania części aplikacji, które są uruchamiane jednorazowo przy pierwszym uruchomieniu aplikacji. UżyjonStart(), 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
ActivityiFragment. Właściciele cyklu życia implementują interfejsLifecycleOwner. - 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. Lifecyclezawierają rzeczywiste stany cyklu życia i wywołują zdarzenia, gdy cykl życia się zmienia.
Aby utworzyć klasę uwzględniającą cykl życia:
- Zaimplementuj interfejs
LifecycleObserverw 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 życiaonStart.
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ę odput, np.putInt(). - Dane możesz odzyskać z pakietu za pomocą metody
onRestoreInstanceState()lub częściej za pomocą metodyonCreate(). MetodaonCreate()ma parametrsavedInstanceState, który zawiera pakiet. - Jeśli zmienna
savedInstanceStatezawieranull, 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ę odget, 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:
- Aktywności (przewodnik po interfejsie API)
Activity(dokumentacja API)- Poznaj cykl życia aktywności
- Obsługa cykli życia za pomocą komponentów uwzględniających cykl życia
LifecycleOwnerLifecycleLifecycleObserveronSaveInstanceState()- Obsługa zmian konfiguracji
- Zapisywanie stanów interfejsu
Inne:
- Timber (GitHub)
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ć?
LifecycleLifecycleOwnerLifecycle.EventLifecycleObserver
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ę:
Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.