Obsługa zmian konfiguracji

Niektóre konfiguracje urządzeń mogą się zmieniać, gdy aplikacja jest uruchomiona. Obejmują one m.in.:

  • Rozmiar interfejsu aplikacji
  • Orientacja ekranu
  • rozmiar i grubość czcionki,
  • Język
  • Tryb ciemny i jasny
  • Dostępność klawiatury

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

Te parametry zwykle wymagają dużych zmian w interfejsie aplikacji, aby platforma Androida miała specjalny mechanizm umożliwiający działanie w razie zmiany. Ten mechanizm to odtworzenie Activity.

Rekreacja

Po zmianie konfiguracji system odtwarza Activity. W tym celu system wywołuje metodę onDestroy() i niszczy istniejącą instancję Activity. Następnie tworzy nową instancję z użyciem onCreate(), która jest inicjowana Activity z nową, zaktualizowaną konfiguracją. Oznacza to również, że system odtworzy interfejs użytkownika z nową konfiguracją.

Pozwala to aplikacji dostosować się do nowych konfiguracji przez automatyczne ponowne załadowanie aplikacji przy użyciu alternatywnych zasobów pasujących do nowej konfiguracji urządzenia.

Przykład dotyczący rekreacji

Weźmy na przykład TextView, który wyświetla statyczny tytuł z użyciem android:text="@string/title", zgodnie z definicją w pliku XML układu. Podczas tworzenia widoku tekst jest ustawiany dokładnie raz na podstawie bieżącego języka. Jeśli język się zmieni, system odtworzy aktywność. W rezultacie system ponownie odtworzy widok i zainicjuje go do prawidłowej wartości na podstawie nowego języka.

Odtworzenie usuwa też każdy stan zachowany jako pola w Activity lub dowolnych zawartych w nim obiektach Fragment, View lub innych. Dzieje się tak, ponieważ odtworzenie Activity powoduje utworzenie zupełnie nowej instancji interfejsu Activity i interfejsu. Poza tym stary element Activity nie jest już widoczny ani prawidłowy, więc wszystkie pozostałe odwołania do niego lub jego obiektów będą nieaktualne. Mogą 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 zapoznać się z informacjami, może to negatywnie wpłynąć na jego wrażenia, gdy wróci do nieczytelnego formularza lub znajdzie się w innym miejscu w aplikacji. Jako deweloper musisz zapewnić spójne wrażenia użytkowników, zmieniając konfigurację i przywracając aktywność.

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

  • Obracanie urządzenia
  • Otwieram tryb wielu okien
  • Zmiana rozmiaru aplikacji w trybie wielu okien lub w dowolnym oknie
  • Składanie składanego urządzenia z kilkoma wyświetlaczami
  • zmienianie motywu systemowego, np. trybu ciemnego lub jasnego;
  • 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 podejścia, które możesz podjąć, aby zachować odpowiedni stan przez ponowne odtworzenie Activity. Wybór sposobu zależy od rodzaju stanu, który chcesz zachować:

  • Trwałość lokalna w przypadku śmierci procesu w przypadku złożonych lub dużych danych. Stała pamięć lokalna obejmuje bazy danych lub DataStore.
  • Przechowywane obiekty, takie jak instancje ViewModel, służące do obsługi stanu związanego z interfejsem w pamięci, gdy użytkownik aktywnie korzysta z aplikacji.
  • Zapisany stan instancji na potrzeby obsługi zakończenia procesu inicjowanego przez system i zachowania stanu przejściowego, który zależy od danych wejściowych lub nawigacji użytkownika.

Aby dowiedzieć się więcej o interfejsach API związanych z każdym z tych interfejsów i o tym, kiedy warto z nich korzystać, przeczytaj artykuł Zapisywanie stanów interfejsu użytkownika.

Ogranicz odtwarzanie aktywności

Możesz uniemożliwić automatyczne odtwarzanie aktywności w przypadku niektórych zmian konfiguracji. Odtworzenie Activity powoduje odtworzenie całego interfejsu użytkownika i wszystkich obiektów, które pochodzą z Activity. Może to być dobry powód, aby tego uniknąć. Na przykład aplikacja może nie wymagać aktualizacji zasobów w przypadku określonej zmiany konfiguracji lub obowiązują Cię ograniczenia wydajności. W takim przypadku możesz zadeklarować, że Twoja aktywność sama obsługuje zmianę konfiguracji i uniemożliwia systemowi jej ponowne uruchomienie.

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

Ten kod manifestu wyłącza odtwarzanie Activity dla MyActivity po zmianie orientacji ekranu i dostępności klawiatury:

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

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

Reagowanie na zmiany konfiguracji w systemie wyświetlania

Gdy w systemie View zajdzie zmiana konfiguracji, w której wyłączysz odtwarzanie Activity, aktywność otrzyma wywołanie Activity.onConfigurationChanged(). Wszystkie dołączone wyświetlenia również będą wywoływać metodę View.onConfigurationChanged(). W przypadku zmian w konfiguracji, które nie zostały dodane do funkcji android:configChanges, system odtwarza działanie tak jak zwykle.

Metoda wywołania zwrotnego onConfigurationChanged() otrzymuje obiekt Configuration, który określa nową konfigurację urządzenia. Przeczytaj pola w obiekcie Configuration, aby określić nową konfigurację. Aby wprowadzić kolejne zmiany, zaktualizuj zasoby, których używasz w interfejsie. Gdy system wywoła tę metodę, obiekt Resources Twojej aktywności zostanie zaktualizowany tak, aby zwracał zasoby na podstawie nowej konfiguracji. Dzięki temu możesz zresetować elementy interfejsu bez ponownego uruchamiania aktywności przez system.

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

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 po wprowadzeniu tych zmian w konfiguracji, możesz zamiast tego wdrożyć onConfigurationChanged(). W takim przypadku wszystkie zasoby używane przed zmianą konfiguracji będą nadal używane, a Ty udało Ci się tylko uniknąć ponownego uruchomienia aktywności. Na przykład aplikacja TV może nie reagować po podłączeniu lub odłączeniu klawiatury Bluetooth.

Zachowaj stan

Stosując tę metodę, musisz zachować stan w trakcie normalnego cyklu aktywności. Dzieje się tak, ponieważ:

  • Nieuniknione zmiany: zmiany w konfiguracji, których nie możesz zapobiec, mogą spowodować ponowne uruchomienie aplikacji.
  • śmierć procesu: aplikacja musi obsługiwać śmierć procesu inicjowanego przez system. Jeśli użytkownik opuści aplikację, a aplikacja będzie działać w tle, system może ją zniszczyć.

Reagowanie na zmiany konfiguracji w Jetpack Compose

Jetpack Compose pozwala Twojej aplikacji łatwiej reagować na zmiany w konfiguracji. Jeśli jednak wyłączysz odtwarzanie Activity w przypadku wszystkich zmian konfiguracji, gdy jest to możliwe, aplikacja nadal będzie prawidłowo obsługiwać te zmiany.

Obiekt Configuration jest dostępny w hierarchii interfejsu tworzenia wiadomości z lokalną kompozycją LocalConfiguration. Gdy tylko się zmieni, funkcje kompozycyjne odczytywane z pliku LocalConfiguration.current tworzą ponownie. Informacje o lokalnym działaniu kompozycji znajdziesz w artykule o danych o zakresie lokalnym w kompozycji CompositionLocal.

Przykład

W poniższym przykładzie funkcja kompozycyjna wyświetla datę w określonym formacie. Obiekt kompozycyjny reaguje na zmiany konfiguracji ustawień regionalnych systemu, wywołując metodę ConfigurationCompat.getLocales() z użyciem polecenia 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ąć przywracania Activity po zmianie języka, obiekt Activity hostujący kod tworzenia wiadomości musi zrezygnować ze zmian konfiguracji języka. Aby to zrobić, ustaw android:configChanges na locale|layoutDirection.

Zmiany konfiguracji: kluczowe pojęcia i sprawdzone metody

Oto kluczowe zagadnienia, które musisz znać podczas pracy nad zmianami w konfiguracji:

  • Konfiguracje: konfiguracje urządzeń określają, jak wyświetla się interfejs użytkownika, np. rozmiar interfejsu aplikacji, język czy motyw systemowy.
  • Zmiany konfiguracji: konfiguracja zmienia się w wyniku interakcji użytkownika. Użytkownik może na przykład zmienić ustawienia urządzenia lub sposób korzystania z urządzenia. Nie da się zapobiec zmianom konfiguracji.
  • Odtwarzanie Activity: zmiany w konfiguracji powodują domyślnie ponowne odtworzenie Activity. To jest wbudowany mechanizm ponownego inicjowania stanu aplikacji na potrzeby nowej konfiguracji.
  • Zniszczenie obiektu Activity: odtwarzanie Activity powoduje, że system niszczy starą instancję Activity i w jej miejsce tworzy nową. Stara instancja jest teraz przestarzała. Wszelkie pozostałe odwołania do niej mogą spowodować wyciek pamięci, błędy lub awarie.
  • Stan: w starej instancji Activity nie ma stanu: w nowej instancji Activity, ponieważ są to 2 różne instancje obiektów. Zachowaj stan aplikacji i użytkownika zgodnie z opisem w sekcji Zapisywanie stanów interfejsu użytkownika.
  • Rezygnacja: rezygnacja z odtwarzania aktywności w przypadku zmiany konfiguracji jest potencjalną optymalizacją. Wymaga ona odpowiedniego aktualizowania aplikacji w odpowiedzi na nową konfigurację.

Aby zadbać o wygodę użytkowników, stosuj te sprawdzone metody:

  • Przygotuj się na częste zmiany w konfiguracji: nie zakładaj, że zmiany w konfiguracji są rzadkie lub nigdy nie występują, niezależnie od poziomu interfejsu API, formatu czy zestawu narzędzi interfejsu. Gdy użytkownik powoduje zmianę konfiguracji, oczekuje, że aplikacje zostaną zaktualizowane i nadal będą prawidłowo działać zgodnie z nową konfiguracją.
  • Zachowaj stan: nie utracisz stanu użytkownika, gdy rozpocznie się odtwarzanie Activity. Zachowaj stan zgodnie z opisem w sekcji Zapisywanie stanów interfejsu użytkownika.
  • Nie rezygnuj z tej funkcji w ramach szybkiego rozwiązania: nie rezygnuj z odtwarzania Activity w celu uniknięcia utraty stanu. Rezygnacja z odtwarzania aktywności wymaga spełnienia obietnicy wprowadzenia zmian, a nadal możesz utracić stan z powodu przywrócenia Activity z powodu innych zmian konfiguracji, śmierci lub zamknięcia aplikacji. Nie można całkowicie wyłączyć rekreacji Activity. Zachowaj stan zgodnie z opisem w sekcji Zapisywanie stanów interfejsu użytkownika.
  • Nie unikaj zmian konfiguracji: nie narzucaj ograniczeń dotyczących orientacji, proporcji obrazu ani możliwości zmiany rozmiaru, aby uniknąć zmian w konfiguracji i przywracania Activity. Ma to negatywny wpływ na użytkowników, którzy chcą korzystać z Twojej aplikacji w wybrany przez siebie sposób.

Obsługa zmian konfiguracji na podstawie rozmiaru

Zmiany konfiguracji na podstawie rozmiaru mogą następować w dowolnym momencie. Są one bardziej prawdopodobne, gdy aplikacja działa na dużym ekranie, na którym użytkownicy mogą włączyć tryb wielu okien. Oczekują, że Twoja aplikacja będzie dobrze działać w danym środowisku.

Wyróżniamy 2 ogólne typy zmian rozmiaru: istotne i nieistotne. Znaczna zmiana rozmiaru polega na zastosowaniu innego zestawu zasobów alternatywnych do nowej konfiguracji z powodu różnicy w rozmiarze ekranu (np. szerokości, wysokości lub najmniejszej szerokości). Zasoby te obejmują elementy zdefiniowane przez aplikację oraz zasoby z jej bibliotek.

Ogranicz odtwarzanie aktywności w celu zmiany konfiguracji na podstawie rozmiaru

Jeśli wyłączysz odtwarzanie Activity w przypadku zmian konfiguracji na podstawie rozmiaru, system nie utworzy ponownie zasobu Activity. Zamiast tego otrzyma wywołanie Activity.onConfigurationChanged(). Wszystkie dołączone widoki będą otrzymywać wywołanie do View.onConfigurationChanged().

Odtwarzanie Activity jest wyłączone w przypadku zmian konfiguracji na podstawie rozmiaru, jeśli plik manifestu zawiera parametr „android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout”.

Zezwalaj na odtwarzanie aktywności w przypadku zmian konfiguracji na podstawie rozmiaru

W Androidzie 7.0 (poziom interfejsu API 24) i nowszych odtworzenie Activity odbywa się tylko w przypadku zmian konfiguracji na podstawie rozmiaru, jeśli jest to znacząca. Jeśli system nie odtworzy Activity z powodu niewystarczającego rozmiaru, może w zamian wywołać metody Activity.onConfigurationChanged() i View.onConfigurationChanged().

Istnieją pewne zastrzeżenia dotyczące wywołań zwrotnych Activity i View gdy parametr Activity nie został odtworzony:

  • W Androidzie od 11 (poziom interfejsu API 30) do 13 (poziom interfejsu API 33) usługa Activity.onConfigurationChanged() nie jest wywoływana.
  • Występuje znany problem, który powoduje, że w niektórych przypadkach funkcja View.onConfigurationChanged() może nie być wywoływana na Androidzie 12L (poziom interfejsu API 32) i wczesnych wersjach Androida 13 (poziom API 33). Więcej informacji znajdziesz w tym publicznym wydaniu. Ten problem został rozwiązany w późniejszych wersjach Androida 13 i 14.

W przypadku kodu, który wymaga nasłuchiwania zmian konfiguracji zależnie od rozmiaru, zalecamy użycie narzędzia View z zastąpionym View.onConfigurationChanged(), zamiast polegać na odtwarzaniu Activity lub Activity.onConfigurationChanged().