Obsługa zmian konfiguracji

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

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

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

Te parametry wymagają zwykle wystarczająco dużych zmian w interfejsie aplikacji, aby platforma Androida miała specjalnie opracowany mechanizm, który pozwala na takie zmiany. Ten mechanizm to Activity odtworzenie.

Rekreacja

System odtwarza Activity po zmianie konfiguracji. W tym celu system wywołuje metodę onDestroy() i niszczy istniejącą instancję Activity. Następnie utworzy nową instancję przy użyciu onCreate(), a ta nowa instancja Activity zostanie zainicjowana z nową, zaktualizowaną konfiguracją. Oznacza to też, że system odtwarza interfejs użytkownika z nową konfiguracją.

Odtwarzanie pomaga aplikacji dostosować się do nowych konfiguracji przez automatyczne ponowne załadowanie aplikacji przy użyciu alternatywnych zasobów, które pasują do nowej konfiguracji urządzenia.

Przykład rekreacyjny

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

Odtwarzanie jest też usuwane ze wszystkich stanów przechowywanych jako pola w Activity lub w dowolnych zawartych w nim obiektach Fragment, View bądź innych. Dzieje się tak, ponieważ odtworzenie Activity tworzy zupełnie nową instancję Activity i interfejsu użytkownika. Poza tym stary Activity nie jest już widoczny ani prawidłowy, więc jego wszystkie odwołania do niego lub zawarte w nim obiekty są nieaktualne. Mogą powodować błędy, wycieki pamięci i awarie.

Oczekiwania użytkowników

Użytkownik aplikacji oczekuje, że stan zostanie zachowany. Gdy użytkownik wypełnia formularz i otwiera inną aplikację w trybie wielu okien, by uzyskać informacje, może to negatywnie wpłynąć na jego wrażenia, jeśli wrócą do wypełnionego formularza lub w całości za pomocą innej aplikacji. Jako deweloper musisz zapewnić spójne środowisko użytkownika przez zmiany konfiguracji i odtwarzanie aktywności.

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

  • Obracanie urządzenia
  • Uruchamiam tryb wielu okien
  • Zmiana rozmiaru aplikacji w trybie wielu okien lub w oknie dowolnym
  • Składanie urządzenia składanego z kilkoma wyświetlaczami
  • zmiana motywu systemowego, np. tryb ciemny lub jasny;
  • Zmiana rozmiaru czcionki
  • Zmienianie języka systemu lub aplikacji
  • Podłączanie i odłączanie klawiatury sprzętowej
  • Podłączanie i odłączanie stacji dokującej

Są 3 główne sposoby pozwalające zachować odpowiedni stan przez odtwarzanie Activity. Wybór zależy od typu stanu, który chcesz zachować:

  • Lokalna trwałość niezbędna do obsługi śmierci procesu w przypadku złożonych lub dużych danych. Trwała pamięć lokalna obejmuje bazy danych lub DataStore.
  • Przechowywane obiekty, takie jak instancje ViewModel, do obsługi stanu związanego z interfejsem użytkownika w pamięci, gdy użytkownik aktywnie korzysta z aplikacji.
  • Zapisano stan instancji na potrzeby obsługi zainicjowanej przez system śmierci procesu i zachowywania stanu przejściowego zależnego od danych wejściowych użytkownika lub nawigacji.

Aby szczegółowo poznać interfejsy API każdego z nich i dowiedzieć się, kiedy ich używasz, przeczytaj artykuł Zapisywanie stanów interfejsu użytkownika.

Ogranicz odtwarzanie aktywności

Możesz zapobiec automatycznemu odtwarzaniu aktywności w przypadku określonych zmian w konfiguracji. Odtworzenie Activity powoduje odtworzenie całego interfejsu użytkownika i wszystkich obiektów uzyskanych z Activity. Możesz mieć ważne powody, aby tego uniknąć. Na przykład aplikacja może nie wymagać aktualizacji zasobów podczas określonej zmiany konfiguracji lub może obowiązywać ograniczenie wydajności. W takim przypadku możesz zadeklarować, że Twoja aktywność obsługuje zmianę samej konfiguracji i uniemożliwia systemowi ponowne uruchomienie aktywności.

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 są podane w dokumentacji atrybutu android:configChanges.

Ten kod manifestu wyłącza odtwarzanie Activity elementu MyActivity w przypadku zmiany 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żesz 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 widoku danych

W systemie View po zmianie konfiguracji, w której wyłączono odtwarzanie Activity, działanie otrzyma wywołanie Activity.onConfigurationChanged(). Do każdego dołączonego widoku zostaną również wysłane wywołanie do View.onConfigurationChanged(). W przypadku zmian konfiguracji, które nie zostały dodane do android:configChanges, system odtwarza tę aktywność w zwykły sposób.

Metoda wywołania zwrotnego onConfigurationChanged() otrzymuje obiekt Configuration, który określa nową konfigurację urządzenia. Przeczytaj pola w obiekcie Configuration, aby sprawdzić nową konfigurację. Aby wprowadzić kolejne zmiany, zaktualizuj zasoby, których używasz w interfejsie. Po wywołaniu tej metody przez system obiekt Resources aktywności zostanie zaktualizowany tak, aby zwracał zasoby zgodnie z nową konfiguracją. Dzięki temu możesz zresetować elementy interfejsu użytkownika bez ponownego uruchamiania Twojej aktywności przez system.

Na przykład taka 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 z powodu tych zmian konfiguracji, nie możesz implementować onConfigurationChanged(). W takim przypadku wszystkie zasoby używane przed zmianą konfiguracji będą nadal używane, a jedynie unikniesz ponownego uruchomienia aktywności. Na przykład aplikacja na telewizory może nie reagować po podłączeniu lub odłączeniu klawiatury Bluetooth.

Zachowaj stan

Gdy korzystasz z tej metody, musisz zachować stan w trakcie normalnego cyklu życia aktywności. Dzieje się tak, ponieważ:

  • Zmiany nieuniknione: zmiany konfiguracji, których nie możesz zapobiec, mogą spowodować ponowne uruchomienie aplikacji.
  • Śmierć procesu: aplikacja musi być w stanie obsłużyć śmierć zainicjowaną przez system. Jeśli użytkownik opuści aplikację i będzie ona działać w tle, system może zniszczyć tę aplikację.
.

Reagowanie na zmiany konfiguracji w Jetpack Compose

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

Obiekt Configuration jest dostępny w hierarchii interfejsu tworzenia wiadomości z lokalną kompozycją LocalConfiguration. Gdy to się zmieni, funkcje kompozycyjne odczytujące dane z LocalConfiguration.current będą ponownie kompilować. Informacje o działaniu zasobów lokalnych kompozycji znajdziesz w artykule Dane o zakresie lokalnym na potrzeby klasy CompositionLocal.

Przykład

W przykładzie poniżej funkcja kompozycyjna wyświetla datę w określonym formacie. Funkcja kompozycyjna reaguje na zmiany konfiguracji języka systemu, wywołując metodę ConfigurationCompat.getLocales() z 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ąć odtworzenia Activity w przypadku zmiany języka, Activity hostujący kod tworzenia musi zrezygnować ze zmian konfiguracji języka. W tym celu ustaw android:configChanges na locale|layoutDirection.

Zmiany w konfiguracji: kluczowe pojęcia i sprawdzone metody

Oto główne pojęcia, które musisz znać podczas pracy nad zmianami konfiguracji:

  • Konfiguracje: konfiguracje urządzeń określają sposób wyświetlania interfejsu użytkownika, np. rozmiar wyświetlacza aplikacji, język czy motyw systemu.
  • Zmiany konfiguracji: konfiguracja zmienia się w wyniku interakcji użytkownika. Na przykład użytkownik może zmienić ustawienia urządzenia lub sposób korzystania z urządzenia. Nie ma sposobu, aby zapobiec zmianom konfiguracji.
  • Odtworzenie Activity: zmiany konfiguracji skutkują domyślnie odtworzeniem Activity. To wbudowany mechanizm ponownego inicjowania stanu aplikacji dla nowej konfiguracji.
  • Zniszczenie Activity: odtworzenie Activity powoduje zniszczenie przez system starej instancji Activity i utworzenie nowej. Stara instancja jest już nieaktualna. Wszelkie pozostałe odwołania do niej mogą spowodować wycieki pamięci, błędy lub awarie.
  • Stan: stan ze starej instancji Activity nie występuje 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: potencjalna optymalizacja może stanowić rezygnację z odtwarzania aktywności w związku z zmianą konfiguracji. Wymaga ona przeprowadzenia odpowiednich aktualizacji 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 wprowadza zmianę konfiguracji, oczekuje, że aplikacje zostaną zaktualizowane i nadal będą działać prawidłowo z nową konfiguracją.
  • Zachowaj stan: nie powoduje utraty stanu użytkownika, gdy dojdzie do odtworzenia Activity. Zachowaj stan w sposób opisany w artykule Zapisywanie stanów interfejsu.
  • Aby uniknąć utraty danych, unikaj rezygnacji z tej funkcji:nie rezygnuj Activityz odtwarzania jako skrótu, aby uniknąć utraty stanu. Rezygnacja z odtwarzania aktywności wymaga wypełnienia obietnicy dotyczącej wprowadzenia zmian. Możesz też utracić stan z powodu odtworzenia Activity innych zmian konfiguracji, procesu śmierci lub zamknięcia aplikacji. Nie można całkowicie wyłączyć przywracania Activity. Zachowaj stan w sposób opisany w artykule Zapisywanie stanów interfejsu.
  • Nie unikaj zmian w konfiguracji: nie ograniczaj orientacji, współczynnika proporcji ani możliwości zmiany rozmiaru, aby uniknąć zmian w konfiguracji i odtworzenia Activity. Ma to negatywny wpływ na użytkowników, którzy chcą korzystać z Twojej aplikacji w preferowany sposób.

Obsługuj zmiany konfiguracji oparte na rozmiarze

Zmiany konfiguracji na podstawie rozmiaru mogą wystąpić w dowolnym momencie. Jest bardziej prawdopodobne, że Twoja aplikacja działa na urządzeniach z dużym ekranem, gdzie użytkownicy mogą włączyć tryb wielu okien. Oczekują, że aplikacja będzie działać w tym środowisku.

Istnieją 2 ogólne typy zmian rozmiaru: zmiany istotne i nieistotne. Znaczna zmiana rozmiaru to sytuacja, w której do nowej konfiguracji zostanie zastosowany inny zestaw zasobów alternatywnych ze względu na różnicę w rozmiarze ekranu, np. szerokość, wysokość lub najmniejszą szerokość. Zasoby te obejmują zarówno te zdefiniowane przez aplikację, jak i te z jej bibliotek.

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

Jeśli wyłączysz odtwarzanie Activity z powodu zmian konfiguracji na podstawie rozmiaru, system nie odtworzy zasobu Activity. Zamiast tego otrzymuje wywołanie Activity.onConfigurationChanged(). Wszystkie dołączone widoki otrzymają wywołanie View.onConfigurationChanged().

Odtwarzanie Activity jest wyłączone w przypadku zmian konfiguracji na podstawie rozmiaru, jeśli w pliku manifestu znajduje się android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout.

Zezwalaj na odtwarzanie aktywności na potrzeby zmian konfiguracji na podstawie rozmiaru

W Androidzie 7.0 (poziom interfejsu API 24) i nowszych odtworzenie Activity ma miejsce tylko w przypadku zmian konfiguracji na podstawie rozmiaru, jeśli zmiana rozmiaru jest znaczna. Jeśli system nie odtworzy obiektu Activity z powodu niewystarczającego rozmiaru, może zamiast niego wywołać Activity.onConfigurationChanged() i View.onConfigurationChanged().

Jeśli dyrektywa Activity nie jest odtworzona, należy zwrócić uwagę na pewne zastrzeżenia związane z wywołaniami zwrotnymi Activity i View:

  • W Androidzie od 11 (poziom interfejsu API 30) do 13 (poziom interfejsu API 33) funkcja Activity.onConfigurationChanged() nie jest wywoływana.
  • Występuje znany problem polegający na tym, ż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 problemie publicznym. Ten problem został już rozwiązany w późniejszych wersjach Androida 13 i 14.

W przypadku kodu zależnego od nasłuchiwania zmian konfiguracji zależnych od rozmiaru zalecamy użycie narzędzia View z zastąpionym elementem View.onConfigurationChanged(), zamiast polegać na Activityodtworzeniu lub Activity.onConfigurationChanged().