Obsługa zmian konfiguracji (widoki)

Pojęcia i implementacja w Jetpack Compose

Niektóre konfiguracje urządzenia mogą się zmieniać podczas działania aplikacji. Obejmują one m.in.:

  • Rozmiar wyświetlania aplikacji
  • Orientacja ekranu
  • Rozmiar i grubość czcionki
  • Język
  • Tryb ciemny a tryb jasny
  • Dostępność klawiatury

Większość tych zmian konfiguracji następuje w wyniku interakcji użytkownika. Na przykład obrócenie lub złożenie urządzenia zmienia ilość miejsca na ekranie dostępnego dla aplikacji. Podobnie zmiana ustawień urządzenia, takich jak rozmiar czcionki, język lub preferowany motyw, zmienia odpowiednie wartości w obiekcie Configuration.

Te parametry zwykle wymagają wystarczająco dużych zmian w interfejsie aplikacji, aby platforma Android miała specjalny mechanizm, który reaguje na ich zmianę. Ten mechanizm to Activityrekonstrukcja.

Odtwarzanie aktywności

Gdy nastąpi zmiana konfiguracji, system ponownie utworzy Activity. W tym celu system wywołuje funkcję onDestroy i niszczy istniejącą instancję Activity. Następnie tworzy nową instancję za pomocą polecenia onCreate, a ta nowa instancja Activity jest inicjowana przy użyciu nowej, zaktualizowanej konfiguracji. Oznacza to również, że system odtwarza interfejs z nową konfiguracją.

Ponowne tworzenie pomaga aplikacji dostosowywać się do nowych konfiguracji, automatycznie przeładowując ją za pomocą alternatywnych zasobów pasujących do nowej konfiguracji urządzenia.

Przykład dotyczący rekreacji

Rozważmy TextView, który wyświetla statyczny tytuł za pomocą elementu android:text="@string/title", zgodnie z definicją w pliku XML układu. Gdy widok zostanie utworzony, tekst zostanie ustawiony dokładnie raz na podstawie bieżącego języka. Jeśli język się zmieni, system ponownie utworzy aktywność. W związku z tym system odtwarza widok i inicjuje go z prawidłową wartością na podstawie nowego języka.

Ponowne utworzenie usuwa też wszystkie stany przechowywane jako pola w obiekcie Activity lub w dowolnym z zawartych w nim obiektów Fragment, View lub innych. Dzieje się tak, ponieważ ponowne utworzenie Activity powoduje utworzenie zupełnie nowej instancji Activity i interfejsu. Poza tym stary element Activity nie jest już widoczny ani ważny, więc wszystkie pozostałe odwołania do niego lub do zawartych w nim obiektów są nieaktualne. Mogą one powodować błędy, wycieki pamięci i awarie.

.

Oczekiwania użytkowników

Użytkownik aplikacji oczekuje, że stan zostanie zachowany. Jeśli użytkownik wypełnia formularz i otwiera inną aplikację w trybie wielu okien, aby sprawdzić informacje, a po powrocie do formularza okazuje się, że został on wyczyszczony lub użytkownik został przeniesiony w inne miejsce w aplikacji, jest to negatywne doświadczenie. Jako deweloper musisz zapewnić spójne wrażenia użytkownika poprzez zmiany konfiguracji i ponowne tworzenie aktywności.

Aby sprawdzić, czy stan jest zachowywany w aplikacji, możesz wykonać działania, które powodują zmiany konfiguracji, zarówno gdy aplikacja działa na pierwszym planie, jak i w tle. Dotyczy to następujących czynności:

  • Obracanie urządzenia
  • Włączanie trybu wielu okien
  • Zmiana rozmiaru aplikacji w trybie wielu okien lub w oknie o dowolnym kształcie
  • Składanie urządzenia składanego z wieloma wyświetlaczami
  • zmiana motywu systemu, np. z trybu ciemnego na jasny;
  • Zmiana rozmiaru czcionki
  • Zmiana języka systemu lub aplikacji
  • Podłączanie i odłączanie klawiatury sprzętowej
  • Podłączanie i odłączanie stacji dokującej

Istnieją 3 główne sposoby zachowania odpowiedniego stanu podczas ponownego tworzenia Activity. Wybór zależy od typu stanu, który chcesz zachować:

  • Lokalne utrwalanie do obsługi śmierci procesu w przypadku złożonych lub dużych danych. Trwałe przechowywanie lokalne obejmuje bazy danych lub DataStore.
  • Obiekty zachowane, takie jak instancje ViewModel, służą do obsługi stanu związanego z interfejsem w pamięci, gdy użytkownik aktywnie korzysta z aplikacji.
  • Zapisany stan instancji, aby obsługiwać śmierć procesu zainicjowaną przez system i zachowywać stan przejściowy, który zależy od danych wejściowych użytkownika lub nawigacji.

Szczegółowe informacje o interfejsach API dla każdego z tych przypadków oraz o tym, kiedy należy ich używać, znajdziesz w artykule Zapisywanie stanów interfejsu.

Reagowanie na zmiany konfiguracji w systemie wyświetlania

W systemie View, gdy nastąpi zmiana konfiguracji, dla której została wyłączona funkcja ponownego tworzenia Activity, aktywność otrzyma wywołanie funkcji Activity.onConfigurationChanged. Wszystkie dołączone widoki również otrzymują wywołanie funkcji View.onConfigurationChanged. W przypadku zmian konfiguracji, które nie zostały dodane do android:configChanges, system ponownie tworzy aktywność w zwykły sposób.

Metoda wywołania zwrotnego onConfigurationChanged otrzymuje obiekt Configuration, który określa nową konfigurację urządzenia. Odczytaj pola w obiekcie Configuration, aby określić nową konfigurację. Aby wprowadzić kolejne zmiany, zaktualizuj zasoby używane w interfejsie. Gdy system wywoła tę metodę, obiekt Resources aktywności zostanie zaktualizowany, aby zwracać zasoby na podstawie nowej konfiguracji. Umożliwia to zresetowanie elementów interfejsu bez ponownego uruchamiania aktywności przez system.

Na przykład ta implementacja onConfigurationChanged sprawdza, czy klawiatura jest dostępna:

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks whether a keyboard is available
    if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
    } else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
    }
}

Java

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks whether a keyboard is available
    if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
    } else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
    }
}

Jeśli nie musisz aktualizować aplikacji na podstawie tych zmian konfiguracji, możesz nie implementować funkcji onConfigurationChanged. W takim przypadku wszystkie zasoby używane przed zmianą konfiguracji są nadal używane, a uniknięto jedynie ponownego uruchomienia aktywności. Na przykład aplikacja TV może nie reagować na podłączenie lub odłączenie klawiatury Bluetooth.

Zachowaj stan

Jeśli używasz tej techniki, musisz zachować stan podczas normalnego cyklu życia aktywności. Dzieje się tak z tych powodów:

  • Nieuniknione zmiany: zmiany konfiguracji, których nie możesz zapobiec, mogą spowodować ponowne uruchomienie aplikacji.
  • Śmierć procesu: aplikacja musi być w stanie poradzić sobie ze śmiercią procesu zainicjowaną przez system. Jeśli użytkownik opuści aplikację i przejdzie ona w tryb działania w tle, system może ją zamknąć.

Zmiany konfiguracji: kluczowe pojęcia i sprawdzone metody

Oto najważniejsze pojęcia, które musisz znać, gdy pracujesz nad zmianami w konfiguracji:

  • Konfiguracje: konfiguracje urządzenia określają sposób wyświetlania interfejsu użytkownikowi, np. rozmiar wyświetlania aplikacji, ustawienia regionalne lub motyw systemu.
  • Zmiany konfiguracji: konfiguracje zmieniają się w wyniku interakcji użytkownika. Na przykład użytkownik może zmienić ustawienia urządzenia lub sposób fizycznego korzystania z niego. Nie można zapobiec zmianom konfiguracji.
  • Activity ponowne tworzenie: zmiany konfiguracji domyślnie powodują Activity. Jest to wbudowany mechanizm ponownej inicjalizacji stanu aplikacji dla nowej konfiguracji.
  • Activity niszczenie: Activity ponowne tworzenie powoduje zniszczenie starej instancji Activity i utworzenie nowej w jej miejsce. Stara instancja jest teraz przestarzała. Wszelkie pozostałe odwołania do niego powodują wycieki pamięci, błędy lub awarie.
  • Stan: stan w starej instancji Activity nie jest obecny w nowej instancji Activity, ponieważ są to 2 różne instancje obiektu. Zachowaj stan aplikacji i użytkownika zgodnie z opisem w sekcji Zapisywanie stanów interfejsu.
  • Rezygnacja: rezygnacja z odtworzenia aktywności w przypadku danego typu zmiany konfiguracji może być optymalizacją. Wymaga to prawidłowego aktualizowania aplikacji w odpowiedzi na nową konfigurację.

Aby zapewnić użytkownikom wygodę, postępuj zgodnie z tymi sprawdzonymi metodami:

  • Przygotuj się na częste zmiany konfiguracji: nie zakładaj, że zmiany konfiguracji są rzadkie lub nigdy się nie zdarzają, niezależnie od poziomu interfejsu API, rodzaju urządzenia czy zestawu narzędzi interfejsu. Gdy użytkownik wprowadza zmianę konfiguracji, oczekuje, że aplikacje zostaną zaktualizowane i będą nadal działać prawidłowo w nowej konfiguracji.
  • Zachowaj stan: nie trać stanu użytkownika, gdy nastąpi ponowne utworzenie Activity. Zachowaj stan zgodnie z opisem w sekcji Zapisywanie stanów interfejsu.
  • Unikaj rezygnacji jako szybkiego rozwiązania: nie rezygnuj z Activity ponownego tworzenia, aby uniknąć utraty stanu. Rezygnacja z ponownego tworzenia aktywności wymaga spełnienia obietnicy obsługi zmiany. Stan może zostać utracony z powodu ponownego tworzenia Activity w wyniku innych zmian konfiguracji, zakończenia procesu lub zamknięcia aplikacji. Całkowite wyłączenie ponownego tworzenia Activity jest niemożliwe. Zachowaj stan zgodnie z opisem w sekcji Zapisywanie stanów interfejsu.
  • Nie unikaj zmian konfiguracji: nie ograniczaj orientacji, proporcji ani możliwości zmiany rozmiaru, aby uniknąć zmian konfiguracji i ponownego tworzenia Activity. Ma to negatywny wpływ na użytkowników, którzy chcą korzystać z aplikacji w preferowany przez siebie sposób.

Obsługa zmian konfiguracji zależnych od rozmiaru

Zmiany konfiguracji oparte na rozmiarze mogą nastąpić w dowolnym momencie i są bardziej prawdopodobne, gdy aplikacja działa na urządzeniu z dużym ekranem, na którym użytkownicy mogą włączyć tryb wielu okien. Oczekują, że Twoja aplikacja będzie dobrze działać w tym środowisku.

Istnieją 2 główne rodzaje zmian rozmiaru: znaczące i nieistotne. Znacząca zmiana rozmiaru to taka, w przypadku której do nowej konfiguracji stosowany jest inny zestaw zasobów alternatywnych ze względu na różnicę w rozmiarze ekranu, np. szerokości, wysokości lub najmniejszej szerokości. Obejmują one zasoby zdefiniowane przez samą aplikację oraz zasoby z jej bibliotek.

Ograniczanie ponownego tworzenia aktywności w przypadku zmian konfiguracji opartych na rozmiarze

Gdy wyłączysz Activity odtwarzanie w przypadku zmian konfiguracji opartych na rozmiarze, system nie odtworzy Activity. Zamiast tego otrzyma połączenie na numer Activity.onConfigurationChanged. Wszystkie dołączone widoki otrzymają wywołanie funkcji View.onConfigurationChanged.

Odtwarzanie Activity jest wyłączone w przypadku zmian konfiguracji opartych na rozmiarze, gdy w pliku manifestu masz android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout".

Zezwalaj na ponowne tworzenie aktywności w przypadku zmian konfiguracji opartych na rozmiarze

Na Androidzie 7.0 (poziom interfejsu API 24) i nowszym Activityponowne tworzenie następuje tylko w przypadku zmian konfiguracji opartych na rozmiarze, jeśli zmiana rozmiaru jest znacząca. Jeśli system nie utworzy elementu Activity z powodu niewystarczającego rozmiaru, może zamiast tego wywołać funkcje Activity.onConfigurationChangedView.onConfigurationChanged.

W przypadku wywołań zwrotnych ActivityView gdy element Activity nie jest ponownie tworzony, obowiązują pewne ograniczenia:

  • W przypadku Androida w wersjach od 11 (poziom API 30) do 13 (poziom API 33) funkcja Activity.onConfigurationChanged nie jest wywoływana.
  • W niektórych przypadkach w Androidzie 12L (poziom API 32) i wczesnych wersjach Androida 13 (poziom API 33) może nie być wywoływana funkcja View.onConfigurationChanged. Więcej informacji znajdziesz w tym publicznym zgłoszeniu. Ten problem został rozwiązany w nowszych wersjach Androida 13 i w Androidzie 14.

W przypadku kodu, który zależy od nasłuchiwania zmian konfiguracji opartych na rozmiarze, zalecamy używanie narzędzia View z zastąpionym parametrem View.onConfigurationChanged zamiast polegać na ponownym tworzeniu Activity lub Activity.onConfigurationChanged.