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
iFragment
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()
ionStop()
, 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
- Otwórz aplikację DessertClicker z ostatniego ćwiczenia z programowania. (Jeśli nie masz aplikacji, pobierz DessertClickerLogs tutaj).
- 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. - 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). - Klasa
DessertTimer
obejmujestartTimer()
istopTimer()
, które uruchamiają i zatrzymują minutnik. GdystartTimer()
działa, licznik czasu wyświetla komunikat dziennika co sekundę z łączną liczbą sekund, które upłynął. Z kolei metodastopTimer()
zatrzymuje działanie licznika czasu oraz instrukcji logu.
- Otwórz aplikację
MainActivity.kt
. U góry klasy, tuż pod zmiennądessertsSold
, dodaj zmienną minutnika:
private lateinit var dessertTimer : DessertTimer;
- Przewiń w dół do
onCreate()
i utwórz nowy obiektDessertTimer
, tuż po wywołaniu adresusetOnClickListener()
:
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.
- W klasie
MainActivity
uruchom licznik czasu w wywołaniu zwrotnymonStart()
:
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}
- Zatrzymaj minutnik w
onStop()
:
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}
- Skompilować i uruchomić aplikację. W Android Studio kliknij panel Logcat. W polu wyszukiwania Logcat wpisz
dessertclicker
, który będzie filtrowany według klasMainActivity
iDessertTimer
. Pamiętaj, że gdy uruchomi się aplikacja, licznik zacznie działać od razu. - 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.
- Użyj ekranu Ostatnie, by wrócić do aplikacji. Zapisz w Logcat, że licznik czasu zaczyna się od 0.
- Kliknij przycisk Udostępnij. Zauważ w Logcat, że licznik czasu nadal działa.
- Kliknij przycisk Strona główna. Komunikat w Logcat, że licznik czasu nie działa.
- Użyj ekranu Ostatnie, by wrócić do aplikacji. Zapisz w Logcat, że licznik uruchamia się z powrotem.
- W metodzie
MainActivity
w metodzieonStop()
dodaj komentarz do wywołaniastopTimer()
. KomentowaniestopTimer()
pokazuje sytuację, w której rozpoczynasz operację wonStart()
, ale pamiętaj, aby zatrzymać ją ponownie wonStop()
. - 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.
- Odznacz wiersz w miejscu
onStop()
, na którym zatrzymujesz minutnik. - Wytnij i wklej połączenie
startTimer()
z numeruonStart()
do aplikacjionCreate()
. Ta zmiana przedstawia przypadek inicjowania i uruchamiania zasobu wonCreate()
, a nie uruchamianiaonCreate()
i uruchamiania go za pomocąonStart()
. - Skompiluj i uruchom aplikację. Zauważ, że minutnik działa zgodnie z oczekiwaniami.
- Kliknij Ekran główny, aby zatrzymać aplikację. Minutnik przestanie działać zgodnie z oczekiwaniami.
- 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ęć wonStop()
.
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
iFragment
są właścicielami cyklu życia. Właściciele cyklu życia stosują interfejsLifecycleOwner
. - 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.
- Otwórz klasę
DesertTimer.kt
. - 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
.
- Pod zmienną
runnable
dodaj blokinit
do definicji klasy. W blokuinit
użyj metodyaddObserver()
, by połączyć obiekt cyklu życia przekazany od właściciela (aktywności) z tą klasą (obserwatorem).
init {
lifecycle.addObserver(this)
}
- Dodaj do elementu
startTimer()
adnotacje@OnLifecycleEvent annotation
i użyj zdarzenia cyklu życiaON_START
. Wszystkie zdarzenia cyklu życia, które może obserwować obserwator cyklu życia, należą do klasyLifecycle.Event
.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
- Wykonaj to samo co
stopTimer()
, używając zdarzeniaON_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
.
- Otwórz aplikację
MainActivity
. W metodzieonCreate()
zmień inicjację elementuDessertTimer
, 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ść.
- Usuń połączenie z numerem
startTimer()
w elemencieonCreate()
i połączenie z numeremstopTimer()
w aplikacjionStop()
. Nie musisz już informować użytkownikaDessertTimer
, 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. - Skompiluj i uruchom aplikację, a następnie otwórz aplikację Logcat. Minutnik zaczął działać zgodnie z oczekiwaniami.
- 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.
- Skompiluj i uruchom aplikację. Kilka razy kliknij babeczkę.
- 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.
- W Android Studio kliknij kartę Terminal, aby otworzyć terminal wiersza poleceń.
- Wpisz
adb
i naciśnij Enter.
Jeśli zobaczysz wiele danych wyjściowych, które zaczynają się odAndroid Debug Bridge version X.XX.X
i kończą natags to be used by logcat (see logcat —h
elp, wszystko jest w porządku. Jeśli zamiast tego widziszadb: command not found
, sprawdź, czy w ścieżce wykonywania jest dostępne polecenieadb
. Instrukcje znajdziesz w sekcji „Dodawanie adb do ścieżki wykonania” w rozdziale Narzędzia. - 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.
- 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()
. - 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ą jakrevenue
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ć.
- W
MainActivity
zastą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 Strona główna, aby umieścić ją w tle. Zwróć uwagę, że wywołanie zwrotne
onSaveInstanceState()
ma miejsce zaraz 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"
Tych kluczy będziesz używać zarówno do zapisywania, jak i pobierania danych z pakietu stanu instancji.
- Przewiń w dół do
onSaveInstanceState()
i zobacz, że parametroutState
ma typBundle
.
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 jakint
iboolean
.
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 aplikacjiTransactionTooLargeException
. - W polu
onSaveInstanceState()
umieść wartośćrevenue
(liczbę całkowitą) w grupie, korzystając z metodyputInt()
:
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.
- 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
- 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.
- Po konfiguracji
DessertTimer
dodaj ten kod do aplikacjionCreate()
:
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.
- 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)
}
- 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.
- Na karcie Terminal w Android Studio uruchom
adb
, aby wyłączyć proces aplikacji.
adb shell am kill com.example.android.dessertclicker
- 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.
- 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 zmiennejallDesserts
.
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.
- W bloku
onCreate()
, który przywraca stan z pakietu, wywołajshowCurrentDessert()
:
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 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
- Skompiluj i uruchom aplikację, a następnie otwórz aplikację Logcat.
- 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). - 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. MainActivity
– dodaj komentarz do całej metodyonSaveInstanceState()
.- 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żyjonSaveInstanceState()
, by umieścić dane aplikacji w pakiecie. Następnie przywróć dane wonCreate()
, aby uniknąć utraty danych o stanie aktywności, gdy urządzenie jest obrócone. - 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ć wonStop()
. - Używaj tagu
onCreate()
tylko raz, aby inicjować te części aplikacji, które działają jeden raz przy pierwszym uruchomieniu. Użyj poleceniaonStart()
, 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
iFragment
. Właściciele cyklu życia stosują interfejsLifecycleOwner
. - 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 życiaonStart
.
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ą funkcjiadb
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ę odput
, np.putInt()
. - Możesz pobrać dane z grupy za pomocą metody
onRestoreInstanceState()
lub częściej w ramach metodyonCreate()
. MetodaonCreate()
zawiera parametrsavedInstanceState
, który zawiera pakiet. - Jeśli zmienna
savedInstanceState
zawieranull
, 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ę odget
, takich jakgetInt()
.
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:
- Działania (przewodnik po interfejsie API)
Activity
(odniesienie do interfejsu API)- Omówienie cyklu życia aktywności
- Obsługa cykli życia z komponentami zależnymi od cyklu życia
LifecycleOwner
Lifecycle
LifecycleObserver
onSaveInstanceState()
- Obsługa zmian w konfiguracji
- Zapisywanie stanów interfejsu
Inne:
- Timber (GitHub)
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ę:
Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.