Zapisywanie stanów interfejsu

W tym przewodniku omawiamy oczekiwania użytkowników dotyczące stanu UI oraz dostępne opcje. do zachowania stanu.

Zapisywanie i przywracanie stanu interfejsu aktywności wkrótce po system niszczy działania lub aplikacje są niezbędne dla dobrego użytkownika, i uzyskiwanie dodatkowych informacji. Użytkownicy oczekują, że stan UI pozostanie taki sam, ale system może zniszczenie aktywności i jej stanu w pamięci.

Aby wypełnić lukę między oczekiwaniami użytkowników a działaniem systemu, skorzystaj z szablonów kombinacji następujących metod:

Optymalne rozwiązanie zależy od złożoności danych interfejsu, użycia aplikacji oraz znalezienie równowagi między szybkością dostępu do danych a wykorzystaniem pamięci.

Upewnij się, że aplikacja spełnia oczekiwania użytkowników i oferuje szybką, szybką, responsywną za pomocą prostego interfejsu online. Unikaj opóźnień podczas wczytywania danych do interfejsu, zwłaszcza w przypadku częstych zmiany konfiguracji, takie jak rotacja.

Oczekiwania użytkowników i działanie systemu

W zależności od wykonywanego działania użytkownik oczekuje, że dany stan działania do usunięcia lub zachowania stanu. W niektórych przypadkach system automatycznie wykonuje to, czego oczekuje użytkownik. W pozostałych przypadkach system w przeciwieństwie do tego, czego oczekuje użytkownik.

Odrzucenie stanu interfejsu inicjowanego przez użytkownika

Użytkownik oczekuje, że po rozpoczęciu działania tymczasowy stan UI działanie pozostaje bez zmian, dopóki użytkownik nie zamknie go całkowicie. Użytkownik może całkowicie odrzucić aktywność, wykonując te czynności:

  • Usunięcie aktywności z ekranu Przegląd (ostatnie).
  • Zamknięcia aplikacji lub wymuszenie jej zamknięcia na ekranie ustawień.
  • Ponownie uruchom urządzenie.
  • Dokończenie jakiegoś procesu działania (wspomagane przez Activity.finish()).

W takich przypadkach odrzucenia przez użytkownika założenie jest takie, na stałe opuścili aktywność i ponownie ją otworzą; oczekuje, że aktywność rozpocznie się od „czystego stanu”. system bazowy, w przypadku tych scenariuszy odrzucenia jest zgodne z oczekiwaniami użytkownika. instancja aktywności zostanie zniszczona i usunięta z pamięci wraz ze wszystkimi zapisany w niej stan oraz wszystkie zapisane rekordy stanu instancji powiązane z działania.

Istnieją pewne wyjątki od tej reguły dotyczące całkowitego odrzucenia – na przykład użytkownik może oczekiwać, że przeglądarka wyświetli mu dokładnie tę stronę, którą przeglądał przed opuszczeniem przeglądarki za pomocą przycisku Wstecz.

Odrzucenie stanu interfejsu inicjowanego przez system

Użytkownik oczekuje, że stan interfejsu działania pozostanie niezmieniony przez cały zmian konfiguracji, takich jak obrót lub przejście do trybu wielu okien. Domyślnie jednak system niszczy aktywność, gdy taka konfiguracja i zostaną usunięte wszystkie stany interfejsu użytkownika zapisane w instancji aktywności. Do aby dowiedzieć się więcej o konfiguracjach urządzeń, zobacz Strona z informacjami o konfiguracji. Uwaga: jest to możliwe (ale niezalecane), aby zastąpić domyślne zachowanie przy zmianach konfiguracji. Patrz sekcja Obsługa zmień konfigurację samodzielnie, by dowiedzieć się więcej.

Użytkownik oczekuje też, że stan interfejsu Twojej aktywności pozostanie taki sam, jeśli: tymczasowo przełączyć się na inną aplikację, a potem wrócić do niej później. Dla: np. użytkownik przeprowadza wyszukiwanie w Twojej aktywności związanej z wyszukiwaniem, a następnie naciska klawisz przycisk ekranu głównego lub odbierze połączenie telefoniczne, gdy użytkownik wróci do aktywności związanej z wyszukiwaniem spodziewa się znaleźć tam słowa kluczowego i jego wyników – dokładnie tak, jak wcześniej.

W takim przypadku aplikacja jest umieszczona w tle, przy czym system da radę aby zachować proces aplikacji w pamięci. System może jednak zniszczyć procesu aplikacji, gdy użytkownik jest poza domem i nie wchodzi w interakcję z innymi aplikacjami. W takim instancja działania zostanie zniszczona wraz ze wszystkimi zapisanymi w niej stanem. Gdy użytkownik ponownie uruchamia aplikację, aktywność jest nieoczekiwanie czyste. Więcej informacji o śmierci procesów znajdziesz w artykule Procesy i cykl życia aplikacji.

Opcje zachowywania stanu UI

Gdy oczekiwania użytkownika dotyczące stanu UI nie odpowiadają domyślnemu systemowi musisz zapisać i przywrócić stan interfejsu użytkownika, aby zniszczenie inicjowane przez system jest widoczne dla użytkownika.

Każda z opcji zachowania stanu interfejsu różni się w zależności od tych wymiarów: wpływające na wrażenia użytkownika.

Wyświetl model Stan zapisanej instancji Pamięć trwała
Lokalizacja pamięci w pamięci w pamięci na dysku lub w sieci
Zmiana konfiguracji przetrwania Tak Tak Tak
przetrwa śmierć procesu zainicjowanego przez system; Nie Tak Tak
Dalej obowiązuje odrzucenie wykonane przez użytkownika/onFinish() Nie Nie Tak
Ograniczenia danych złożone obiekty są odpowiednie, ale przestrzeń jest ograniczona przez dostępną pamięć tylko w przypadku typów podstawowych oraz prostych, małych obiektów, takich jak String. ograniczone tylko przez miejsce na dysku lub koszt / czas pobierania z zasobu sieciowego
Czas odczytu/zapisu szybki (tylko dostęp do pamięci) wolno (wymaga serializacji/deserializacji) wolne (wymaga dostępu do dysku lub transakcji sieciowej)

Używaj ViewModel do obsługi zmian konfiguracji

Model widoku danych idealnie nadaje się do przechowywania danych związanych z interfejsem użytkownika i zarządzania nimi, gdy aktywne korzystanie z aplikacji. Zapewnia szybki dostęp do danych interfejsu i pomaga unikanie ponownego pobierania danych z sieci lub dysku między rotacją, zmianą rozmiaru okna inne typowe zmiany konfiguracji. Aby dowiedzieć się, jak zaimplementować ViewModel: zapoznaj się z przewodnikiem ViewModel.

ViewModel zachowuje dane w pamięci, co oznacza, że ich pobranie jest tańsze niż z dysku lub sieci. Model widoku danych jest powiązany z działaniem (lub inny właściciel cyklu życia) – pozostaje w pamięci podczas konfiguracji a system automatycznie powiąże ViewModel z nowym wystąpienia aktywności, które jest wynikiem zmiany konfiguracji.

Modele View są automatycznie niszczone przez system, gdy użytkownik wycofuje się Twojej aktywności lub fragmentu albo wywołujesz funkcję finish(), która oznacza, że stan to są usuwane zgodnie z oczekiwaniami użytkownika w tych sytuacjach.

W przeciwieństwie do zapisanego stanu instancji modele ViewModel są niszczone podczas inicjowania przez system śmierć procesu. Aby ponownie załadować dane po zainicjowanym przez system śmierci procesu w ViewModel, użyj interfejsu API SavedStateHandle. Jeśli dane są też związane z interfejsem użytkownika i nie muszą być trzymane w modelu widoku danych, należy użyć funkcji onSaveInstanceState() w systemie widoku lub rememberSaveable w Jetpack Utwórz. Jeśli dane to dane aplikacji, lepiej jest je zachować na dysk.

Jeśli masz już rozwiązanie w pamięci do zapisywania stanu interfejsu użytkownika między zmianami konfiguracji może nie być konieczne używanie modelu ViewModel.

Używaj stanu zapisanej instancji jako kopii zapasowej do obsługi śmierci procesu inicjowanego przez system

wywołanie zwrotne onSaveInstanceState() w systemie widoku danych, rememberSaveable w Jetpack Compose i SavedStateHandle w Modele widoków przechowują dane potrzebne do ponownego załadowania stanu kontrolera interfejsu, np. działania lub fragmentu, jeśli system zniszczy je, a następnie ponownie odtworzy kontrolerem. Aby dowiedzieć się, jak wdrożyć stan zapisanej instancji za pomocą onSaveInstanceState, przeczytaj sekcję Zapisywanie i przywracanie stanu aktywności w Przewodnik dotyczący cyklu życia aktywności.

Zapisane pakiety stanów instancji są zachowywane zarówno w wyniku zmian konfiguracji, śmierci w procesach, ale są ograniczone pamięcią i szybkością, ponieważ różne interfejsy API zserializować dane. Serializacja może zużywać dużo pamięci, jeśli obiekty są bardzo skomplikowane. Ponieważ ten proces odbywa się w wątku głównym podczas zmiany konfiguracji długotrwałe serializacji może spowodować i zacinanie się obrazu.

Nie używaj zapisanego stanu instancji do przechowywania dużych ilości danych, takich jak mapy bitowe, ani złożonych struktur danych, które wymagają długiej serializacji lub deserializacja. Przechowuj tylko typy podstawowe oraz proste, małe obiekty na przykład String. W związku z tym używaj zapisanego stanu instancji do przechowywania niezbędne do odtworzenia danych użytkownika, np. identyfikatora w przypadku awarii innych mechanizmów trwałości. Większość aplikacje powinny wdrożyć to rozwiązanie, aby obsługiwać proces zainicjowany przez system.

W zależności od przypadków użycia aplikacji może nie być konieczne używanie zapisanych instancji stanu. Przeglądarka może na przykład wyświetlić użytkownikowi dokładnie tę stronę, stronę przeglądaną przed opuszczeniem przeglądarki. Jeśli Twoja aktywność działa w ten sposób, możesz zrezygnować z użycia zapisanego stanu instancji i zamiast tego wszystko lokalnie.

Poza tym gdy otworzysz aktywność z intencji, pakiet dodatków będzie miał jest dostarczana do aktywności zarówno po zmianie konfiguracji, jak i po zmianie system przywraca aktywność. Jeśli dane o stanie interfejsu użytkownika, takie jak wyszukiwanie były przekazywane jako dodatkowe intencje przy uruchamianiu działania, może użyć pakietu dodatków zamiast pakietu zapisanego stanu instancji. Aby się uczyć więcej informacji o dodatkach do intencji, przeczytaj artykuł o filtrach intencji i zamiarów.

W każdym z tych scenariuszy należy nadal używać narzędzia ViewModel, aby unikać marnowanie czasu na ponowne ładowanie danych z bazy danych podczas zmiany konfiguracji.

Jeśli dane interfejsu użytkownika są proste i niepełne, możesz użyć zapisanych interfejsów API stanu instancji w celu zachowania danych o stanie.

Łączenie z zapisanym stanem za pomocą SavedStateRegistry

Rozpoczyna się od Fragmentu 1.1.0 lub jego zależność przejściowa Activity 1.0.0, kontrolery UI, takie jak Activity lub Fragment, implementują SavedStateRegistryOwner i zastosuj SavedStateRegistry, powiązane z tym kontrolerem. SavedStateRegistry umożliwia podłączanie komponentów zapisany stan kontrolera UI, aby mógł być wykorzystywany lub wspomagany. Przykład: moduł Saved State dla modelu ViewModel używa SavedStateRegistry do utworzenia SavedStateHandle i przekaż go obiektom ViewModel. Możesz pobrać SavedStateRegistry z poziomu kontrolera UI, wywołując getSavedStateRegistry()

Komponenty, które przyczyniają się do zmiany stanu, muszą zaimplementować SavedStateRegistry.SavedStateProvider, która definiuje pojedynczą metodę pod tytułem saveState(). Metoda saveState() umożliwia komponentowi: zwraca element Bundle zawierający dowolny stan, który powinien zostać zapisany z tego komponentu. SavedStateRegistry wywołuje tę metodę na etapie zapisywania interfejsu użytkownika. w cyklu życia kontrolera.

Kotlin

class SearchManager : SavedStateRegistry.SavedStateProvider {
    companion object {
        private const val QUERY = "query"
    }

    private val query: String? = null

    ...

    override fun saveState(): Bundle {
        return bundleOf(QUERY to query)
    }
}

Java

class SearchManager implements SavedStateRegistry.SavedStateProvider {
    private static String QUERY = "query";
    private String query = null;
    ...

    @NonNull
    @Override
    public Bundle saveState() {
        Bundle bundle = new Bundle();
        bundle.putString(QUERY, query);
        return bundle;
    }
}

Aby zarejestrować SavedStateProvider, zadzwoń pod numer registerSavedStateProvider(): SavedStateRegistry, przekazując klucz do powiązania z danymi dostawcy jako jak również usługodawcy. Zapisane wcześniej dane dostawcy można pobrane z zapisanego stanu przez wywołanie consumeRestoredStateForKey() w SavedStateRegistry, przekazując klucz powiązany z kluczem dostawcy i skalowalnych danych.

W: Activity lub Fragment możesz zarejestrować SavedStateProvider w onCreate() po rozmowie z: super.onCreate(). Możesz też ustawić LifecycleObserver na elemencie SavedStateRegistryOwner, który implementuje LifecycleOwner i zarejestruj SavedStateProvider po Występuje zdarzenie ON_CREATE. Za pomocą LifecycleObserver możesz odłączyć rejestracji i pobrania wcześniej zapisanego stanu z SavedStateRegistryOwner.

Kotlin

class SearchManager(registryOwner: SavedStateRegistryOwner) : SavedStateRegistry.SavedStateProvider {
    companion object {
        private const val PROVIDER = "search_manager"
        private const val QUERY = "query"
    }

    private val query: String? = null

    init {
        // Register a LifecycleObserver for when the Lifecycle hits ON_CREATE
        registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_CREATE) {
                val registry = registryOwner.savedStateRegistry

                // Register this object for future calls to saveState()
                registry.registerSavedStateProvider(PROVIDER, this)

                // Get the previously saved state and restore it
                val state = registry.consumeRestoredStateForKey(PROVIDER)

                // Apply the previously saved state
                query = state?.getString(QUERY)
            }
        }
    }

    override fun saveState(): Bundle {
        return bundleOf(QUERY to query)
    }

    ...
}

class SearchFragment : Fragment() {
    private var searchManager = SearchManager(this)
    ...
}

Java

class SearchManager implements SavedStateRegistry.SavedStateProvider {
    private static String PROVIDER = "search_manager";
    private static String QUERY = "query";
    private String query = null;

    public SearchManager(SavedStateRegistryOwner registryOwner) {
        registryOwner.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> {
            if (event == Lifecycle.Event.ON_CREATE) {
                SavedStateRegistry registry = registryOwner.getSavedStateRegistry();

                // Register this object for future calls to saveState()
                registry.registerSavedStateProvider(PROVIDER, this);

                // Get the previously saved state and restore it
                Bundle state = registry.consumeRestoredStateForKey(PROVIDER);

                // Apply the previously saved state
                if (state != null) {
                    query = state.getString(QUERY);
                }
            }
        });
    }

    @NonNull
    @Override
    public Bundle saveState() {
        Bundle bundle = new Bundle();
        bundle.putString(QUERY, query);
        return bundle;
    }

    ...
}

class SearchFragment extends Fragment {
    private SearchManager searchManager = new SearchManager(this);
    ...
}

Używaj lokalnej trwałości, aby radzić sobie ze śmiercią procesów w przypadku złożonych lub dużych danych

Trwała pamięć lokalna, taka jak baza danych lub współdzielone ustawienia, będzie nadal działać tak długo, jak długo aplikacja jest zainstalowana na urządzeniu użytkownika (chyba że użytkownik usuwa dane aplikacji). Mimo że taka pamięć lokalna działa działania inicjowane przez system i proces aplikacji mogą być kosztowne, ponieważ musi zostać odczytany z pamięci lokalnej. Często ta trwała pamięć lokalna może już być częścią Twojej aplikacji do przechowywania wszystkich danych, których nie chcesz utracić po otwarciu i zamknięciu działania.

Ani ViewModel, ani zapisany stan instancji nie są długoterminowymi rozwiązaniami do przechowywania danych. dlatego nie zastępują one pamięci lokalnej, takiej jak baza danych. Zamiast Ciebie powinni używać tych mechanizmów tylko do tymczasowego przechowywania informacji tylko o tymczasowym stanie UI oraz używanie pamięci trwałej do przechowywania innych danych aplikacji. Zobacz Przewodnik po architekturze aplikacji. , aby dowiedzieć się więcej o tym, jak wykorzystać pamięć lokalną do utrwalenia modelu aplikacji długoterminowe (np. po ponownym uruchomieniu urządzenia).

Zarządzanie stanem interfejsu: dziel i podbijaj

Możesz skutecznie zapisywać i przywracać stan interfejsu, dzieląc pracę między różnych typów mechanizmów trwałości. W większości przypadków każdy z tych mechanizmów powinien zapisywać inny typ danych używanych w ramach aktywności w zależności od wadliwej złożoności danych, szybkości dostępu i czasu użytkowania.

  • Trwałość lokalna: przechowuje wszystkie dane aplikacji, których nie chcesz utracić, jeśli możesz otworzyć i zamknąć aktywność.
    • Przykład: zbiór obiektów utworów, który może zawierać pliki audio i metadanych.
  • ViewModel: przechowuje w pamięci wszystkie dane potrzebne do wyświetlenia powiązany UI, stan interfejsu ekranu.
    • Przykład: obiekty utworu z ostatniego i ostatniego wyszukiwania wyszukiwanego hasła.
  • Zapisany stan instancji: przechowuje niewielką ilość danych potrzebnych do ponownego załadowania Stan interfejsu, jeśli system zatrzyma, a następnie ponownie utworzy interfejs. Zamiast przechowywać dane złożone obiekty, utrwalaj złożone obiekty w pamięci lokalnej unikalny identyfikator tych obiektów w interfejsach API zapisanych stanów instancji.
    • Przykład: przechowywanie ostatniego zapytania.

Weźmy jako przykład działanie pozwalające na przeszukiwanie z biblioteki utworów. Oto jak powinny być obsługiwane różne zdarzenia:

Gdy użytkownik doda utwór, ViewModel natychmiast przekaże dostęp do trwałego lokalnie. Jeśli nowo dodany utwór powinien być widoczny w interfejsie, powinien również zaktualizować dane w obiekcie ViewModel, aby odzwierciedlić dodanie funkcji tę piosenkę. Pamiętaj, aby wszystkie wstawienia bazy danych przeprowadzać poza wątkiem głównym.

Gdy użytkownik wyszuka jakiś utwór, złożone dane o utworze, które zostaną wczytane w bazie danych, powinien być natychmiast zapisany w obiekcie ViewModel w ramach stan UI ekranu.

Gdy aktywność przechodzi w tle, a system wywołuje zapisane interfejsów API stanu instancji, wyszukiwane hasło powinno być zapisane w zapisanym stanie instancji, na wypadek gdyby proces się powtarzał. Ponieważ te informacje są niezbędne do wczytywania danych aplikacji utrwalonych w tym modelu, zapisz zapytanie w modelu ViewModel SavedStateHandle To wszystkie informacje potrzebne do wczytywania danych przywrócić interfejs do aktualnego stanu.

Przywróć złożone stany: ponowne składanie elementów

Gdy nadejdzie pora, aby użytkownik wrócił do aktywności, masz 2 możliwości: scenariuszy odtworzenia aktywności:

  • Po zatrzymaniu przez system aktywność jest odtwarzana ponownie. system ma zapisane zapytanie w zapisanym pakiecie stanu instancji, a interfejs użytkownika powinno przekazać zapytanie do funkcji ViewModel, jeśli SavedStateHandle nie jest używany. ViewModel widzi, że nie ma w pamięci podręcznej wyników wyszukiwania, a delegaci wczytywania wyników wyszukiwania za pomocą podanego zapytania.
  • Aktywność jest tworzona po zmianie konfiguracji. Od ViewModel instancja nie została zniszczona, ViewModel przechowuje wszystkie informacje w pamięci podręcznej i nie musi ponownie wysyłać zapytań do bazy danych.
.

Dodatkowe materiały

Więcej informacji o zapisywaniu stanów interfejsu użytkownika znajdziesz w tych materiałach.

Blogi

. .