Obsługa zmian konfiguracji

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ą 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 powoduje też wyczyszczenie wszystkich stanów przechowywanych 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 że użytkownik został przeniesiony w inne miejsce w aplikacji, jest to negatywne doświadczenie. Jako deweloper musisz zadbać o spójność informacji poprzez zmiany konfiguracji i ponowne tworzenie aktywności.

Aby sprawdzić, czy stan jest zachowywany w aplikacji, możesz wykonać działania powodujące zmiany konfiguracji zarówno wtedy, gdy aplikacja jest 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 komponentu.Activity Wybór zależy od rodzaju 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 zachowywane, takie jak instancje ViewModel, 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.

Ograniczanie ponownego tworzenia aktywności

Możesz zapobiec automatycznemu ponownemu tworzeniu aktywności w przypadku niektórych zmian konfiguracji. Activity powoduje ponowne utworzenie całego interfejsu i wszystkich obiektów pochodzących z Activity. Możesz mieć ku temu dobre powody. Na przykład aplikacja może nie wymagać aktualizacji zasobów podczas określonej zmiany konfiguracji lub może mieć ograniczenia wydajności. W takim przypadku możesz zadeklarować, że aktywność sama obsługuje zmianę konfiguracji, i zapobiec ponownemu uruchomieniu aktywności przez system.

Aby wyłączyć ponowne tworzenie aktywności w przypadku określonych zmian konfiguracji, dodaj typ konfiguracji do parametru android:configChanges we wpisie <activity> w pliku AndroidManifest.xml. Możliwe wartości znajdziesz w dokumentacji atrybutu android:configChanges.

Poniższy kod pliku manifestu wyłącza Activity odtwarzanie w przypadku MyActivity, gdy zmienia się orientacja ekranu i dostępność klawiatury:

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:label="@string/app_name">

Niektóre zmiany konfiguracji zawsze powodują ponowne uruchomienie aktywności. Nie możesz ich wyłączyć. Nie możesz na przykład wyłączyć dynamicznej zmiany kolorów wprowadzonej w Androidzie 12L (poziom API 32).

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órych nie dodano do android:configChanges, system odtwarza 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ąć.

Reagowanie na zmiany konfiguracji w Jetpack Compose

Jetpack Compose ułatwia reagowanie aplikacji na zmiany konfiguracji. Jeśli jednak wyłączysz Activity w przypadku wszystkich zmian konfiguracji, w których jest to możliwe, aplikacja nadal musi prawidłowo obsługiwać zmiany konfiguracji.

Obiekt Configuration jest dostępny w hierarchii interfejsu Compose z LocalConfiguration lokalnym komponentem. Gdy się zmieni, funkcje kompozycyjne odczytujące wartość LocalConfiguration.current zostaną ponownie skomponowane. Więcej informacji o działaniu lokalnych kompozycji znajdziesz w artykule Dane o lokalnym zakresie z użyciem CompositionLocal.

Przykład

W tym przykładzie funkcja kompozycyjna wyświetla datę w określonym formacie. Kompozycja reaguje na zmiany konfiguracji języka systemu, wywołując funkcję ConfigurationCompat.getLocales() z parametrem LocalConfiguration.current.

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

Aby uniknąć ponownego tworzenia Activity po zmianie ustawień regionalnych, Activity hostujący kod Compose musi zrezygnować ze zmian konfiguracji regionalnych. Aby to zrobić, ustaw wartość android:configChanges na locale|layoutDirection.

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 powodują domyślne Activity ponowne tworzenie. Jest to wbudowany mechanizm ponownego inicjowania stanu aplikacji w przypadku nowej konfiguracji.
  • Activity niszczenie: Activity ponowne tworzenie powoduje zniszczenie starej instancji i utworzenie w jej miejsce nowej.Activity Stara instancja jest już 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, formatu 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 jako skrótu, 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 nowszych wersjach Activityponowne tworzenie następuje tylko w przypadku zmian konfiguracji opartych na rozmiarze, jeśli zmiana rozmiaru jest znacząca. Jeśli system nie może odtworzyć Activity z powodu niewystarczającego rozmiaru, może zamiast tego wywołać Activity.onConfigurationChanged()View.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().