Aby odpowiedzieć na to pytanie, musisz zagłębić się w LoaderManager
kod. Podczas gdy dokumentacja dla samego LoaderManagera nie jest wystarczająco jasna (lub nie byłoby tego pytania), dokumentacja LoaderManagerImpl, podklasy abstrakcyjnego LoaderManagera, jest znacznie bardziej pouczająca.
initLoader
Wywołanie, aby zainicjować określony identyfikator za pomocą modułu ładującego. Jeśli ten identyfikator ma już powiązany z nim moduł ładujący, pozostaje niezmieniony, a wszelkie poprzednie wywołania zwrotne zastępowane są nowo podanymi. Jeśli obecnie nie ma programu ładującego dla tego identyfikatora, zostanie utworzony i uruchomiony nowy program ładujący.
Tej funkcji należy zasadniczo używać podczas inicjowania komponentu, aby mieć pewność, że zostanie utworzony moduł ładujący, na którym się ona opiera. Pozwala to na ponowne wykorzystanie istniejących danych modułu ładującego, jeśli już takie istnieją, dzięki czemu na przykład w przypadku ponownego utworzenia działania po zmianie konfiguracji nie ma potrzeby ponownego tworzenia jego programów ładujących.
restartLoader
Zadzwoń, aby odtworzyć moduł ładujący powiązany z określonym identyfikatorem. Jeśli obecnie istnieje Loader powiązany z tym ID, zostanie on odpowiednio anulowany / zatrzymany / zniszczony. Zostanie utworzony nowy Loader z podanymi argumentami, a jego dane dostarczone do Ciebie, gdy będą dostępne.
[...] Po wywołaniu tej funkcji wszystkie poprzednie programy ładujące skojarzone z tym identyfikatorem zostaną uznane za nieważne i nie będziesz otrzymywać od nich dalszych aktualizacji danych.
Zasadniczo istnieją dwa przypadki:
- Program ładujący z identyfikatorem nie istnieje: obie metody utworzą nowy moduł ładujący, więc nie ma różnicy
- Program ładujący o identyfikatorze już istnieje:
initLoader
zastąpi tylko wywołania zwrotne przekazane jako parametr, ale nie anuluje ani nie zatrzyma programu ładującego. Dla a CursorLoader
oznacza to, że kursor pozostaje otwarty i aktywny (jeśli tak było przed initLoader
wywołaniem). Z drugiej strony, restartLoader anuluje, zatrzyma i zniszczy program ładujący (i zamknie podstawowe źródło danych jak kursor) i utworzy nowy moduł ładujący (który również utworzy nowy kursor i ponownie uruchomi zapytanie, jeśli moduł ładujący jest CursorLoader).
Oto uproszczony kod dla obu metod:
initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
restartLoader
LoaderInfo info = mLoaders.get(id);
if (info != null) {
LoaderInfo inactive = mInactiveLoaders.get(id);
if (inactive != null) {
} else {
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
}
}
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
Jak widać w przypadku, gdy moduł ładujący nie istnieje (info == null), obie metody utworzą nowy moduł ładujący (info = createAndInstallLoader (...)). W przypadku, gdy program ładujący już istnieje, initLoader
zastępuje tylko wywołania zwrotne (info.mCallbacks = ...), jednocześnie restartLoader
dezaktywuje stary program ładujący (zostanie zniszczony, gdy nowy program ładujący zakończy swoją pracę), a następnie tworzy nowy.
W związku z tym jest teraz jasne, kiedy initLoader
i kiedy używać restartLoader
i dlaczego warto mieć te dwie metody.
initLoader
służy do upewnienia się, że istnieje zainicjowany moduł ładujący. Jeśli żaden nie istnieje, tworzony jest nowy, jeśli już istnieje, jest ponownie używany. Zawsze używamy tej metody, chyba że potrzebujemy nowego modułu ładującego, ponieważ zmieniło się zapytanie do uruchomienia (nie dane bazowe, ale rzeczywiste zapytanie, jak w instrukcji SQL dla CursorLoader), w którym to przypadku będziemy wywoływać restartLoader
.
Aktywny / Fragment cyklu życia nie ma nic wspólnego z decyzją do korzystania z jednej lub drugiej metody (i nie ma potrzeby, aby śledzić połączeń za pomocą znacznika jeden strzał jak sugeruje Simon)! Ta decyzja jest podejmowana wyłącznie na podstawie „zapotrzebowania” na nowy program ładujący. Jeśli chcemy uruchomić to samo zapytanie, którego używamy initLoader
, jeśli chcemy uruchomić inne zapytanie, którego używamy restartLoader
.
Zawsze moglibyśmy użyć, restartLoader
ale byłoby to nieefektywne. Po obróceniu ekranu lub jeśli użytkownik opuści aplikację i wróci później do tego samego działania, zwykle chcemy pokazać ten sam wynik zapytania, więc restartLoader
niepotrzebnie ponownie utworzymy moduł ładujący i odrzucimy podstawowy (potencjalnie kosztowny) wynik zapytania.
Bardzo ważne jest, aby zrozumieć różnicę między ładowanymi danymi a „zapytaniem” w celu załadowania tych danych. Załóżmy, że używamy CursorLoader odpytującego tabelę o zamówienia. Jeśli do tej tabeli zostanie dodane nowe zamówienie, CursorLoader używa onContentChanged () do poinformowania interfejsu użytkownika o aktualizacji i wyświetleniu nowej kolejności (nie ma potrzeby używania restartLoader
w tym przypadku). Jeśli chcemy wyświetlić tylko otwarte zamówienia, potrzebujemy nowego zapytania i restartLoader
użylibyśmy do zwrócenia nowego CursorLoader odzwierciedlającego nowe zapytanie.
Czy istnieje związek między tymi dwiema metodami?
Dzielą się kodem, aby utworzyć nowy moduł ładujący, ale robią inne rzeczy, gdy program ładujący już istnieje.
Czy dzwonienie restartLoader
zawsze dzwoni initLoader
?
Nie, nigdy tak nie jest.
Czy mogę zadzwonić restartLoader
bez dzwonienia initLoader
?
Tak.
Czy można bezpiecznie zadzwonić initLoader
dwa razy, aby odświeżyć dane?
Można bezpiecznie zadzwonić initLoader
dwa razy, ale żadne dane nie zostaną odświeżone.
Kiedy powinienem użyć jednego z dwóch i dlaczego ?
To powinno (miejmy nadzieję) być jasne po moich wyjaśnieniach powyżej.
Zmiany konfiguracji
LoaderManager zachowuje swój stan niezależnie od zmian konfiguracji (w tym zmian orientacji), więc można by pomyśleć, że nie mamy już nic do zrobienia. Pomyśl jeszcze raz ...
Po pierwsze, LoaderManager nie zachowuje wywołań zwrotnych, więc jeśli nic nie zrobisz, nie będziesz otrzymywać wywołań do metod wywołania zwrotnego, takich jak onLoadFinished()
i tym podobne, a to najprawdopodobniej zepsuje twoją aplikację.
Dlatego MUSIMY wywołać przynajmniej initLoader
przywrócenie metod wywołania zwrotnego (a restartLoader
jest oczywiście również możliwe). W dokumentacji czytamy:
Jeśli w momencie wywołania wywołujący jest w stanie uruchomionym, a żądany program ładujący już istnieje i wygenerował swoje dane, wówczas wywołanie zwrotne onLoadFinished(Loader, D)
zostanie wywołane natychmiast (wewnątrz tej funkcji) [...].
Oznacza to, że jeśli zadzwonimy initLoader
po zmianie orientacji, onLoadFinished
od razu otrzymamy połączenie, ponieważ dane są już załadowane (zakładając, że tak było przed zmianą). Chociaż brzmi to prosto, może być trudne (nie wszyscy kochamy Androida ...).
Musimy rozróżnić dwa przypadki:
- Sam obsługuje zmiany konfiguracji: dotyczy to fragmentów, które używają setRetainInstance (true) lub działania z odpowiednim
android:configChanges
tagiem w manifeście. Te komponenty nie otrzymają wywołania onCreate po np. Obróceniu ekranu, więc pamiętaj o wywołaniu initLoader/restartLoader
innej metody wywołania zwrotnego (np
onActivityCreated(Bundle)
. In
). Aby móc zainicjalizować program ładujący (y), identyfikatory modułu ładującego muszą być przechowywane (np. W liście). Ponieważ komponent jest zachowywany przy zmianach konfiguracji, możemy po prostu zapętlić istniejące identyfikatory modułu ładującego i wywołać initLoader(loaderid,
...)
.
- Sam nie obsługuje zmian konfiguracji: w tym przypadku Loadery można zainicjować w onCreate, ale musimy ręcznie zachować identyfikatory modułu ładującego lub nie będziemy w stanie wykonać wymaganych wywołań initLoader / restartLoader. Jeśli identyfikatory są przechowywane w
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
tablicy ArrayList, wykonalibyśmy operację onSaveInstanceState i przywrócilibyśmy je w onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
zanim wykonamy wywołanie (a) initLoader.
initLoader
(i wszystkie wywołania zwrotne zostały zakończone, program ładujący jest bezczynny) po rotacji, nie otrzymaszonLoadFinished
oddzwonienia, ale jeśli użyjeszrestartLoader
go?Wywołanie,
initLoader
gdy moduł ładujący został już utworzony (zwykle dzieje się to na przykład po zmianach konfiguracji), informuje moduł ładujący, aby natychmiast dostarczył najnowsze dane modułu ładującegoonLoadFinished
. JeśliinitLoader
moduł ładujący nie został jeszcze utworzony (na przykład podczas pierwszego uruchomienia działania / fragmentu), wywołanie mówi LoaderManagerowi, aby wywołał wonCreateLoader
celu utworzenia nowego modułu ładującego .Wywołanie
restartLoader
niszczy już istniejący moduł ładujący (jak również wszelkie istniejące dane z nim skojarzone) i nakazuje modułowi ładującemu LoaderManager wywołanie wonCreateLoader
celu utworzenia nowego modułu ładującego i zainicjowania nowego obciążenia.Dokumentacja jest również dość jasna:
initLoader
zapewnia, że Loader jest zainicjowany i aktywny. Jeśli moduł ładujący jeszcze nie istnieje, zostanie utworzony i (jeśli czynność / fragment jest aktualnie uruchomiony) uruchamia moduł ładujący. W przeciwnym razie ostatnio utworzony program ładujący jest ponownie używany.restartLoader
uruchamia nowy lub restartuje istniejący Loader w tym menedżerze, rejestruje do niego wywołania zwrotne i (jeśli czynność / fragment jest aktualnie rozpoczęty) zaczyna go ładować. Jeśli program ładujący o tym samym identyfikatorze został wcześniej uruchomiony, zostanie automatycznie zniszczony, gdy nowy program ładujący zakończy swoją pracę. Wywołanie zwrotne zostanie dostarczone przed zniszczeniem starego programu ładującego.źródło
initLoader()
wonCreate()
/onActivityCreated()
podczas pierwszego uruchomienia działania / fragmentu. W ten sposób, gdy użytkownik po raz pierwszy otworzy działanie, moduł ładujący zostanie utworzony po raz pierwszy ... ale przy każdej późniejszej zmianie konfiguracji, w której cała czynność / fragment musi zostać zniszczona, następujące wywołanieinitLoader()
po prostu zwróci stareLoader
zamiast tworzenie nowego. Zwykle używasz,restartLoader()
gdy musisz zmienićLoader
zapytanie (np. Chcesz uzyskać przefiltrowane / posortowane dane itp.).Niedawno napotkałem problem z wieloma menedżerami modułu ładującego i zmianami orientacji ekranu i chciałbym powiedzieć, że po wielu próbach i błędach następujący wzorzec działa dla mnie zarówno w działaniach, jak i we fragmentach:
onCreate: call initLoader(s) set a one-shot flag onResume: call restartLoader (or later, as applicable) if the one-shot is not set. unset the one-shot in either case.
(innymi słowy, ustaw jakąś flagę, aby initLoader był zawsze uruchamiany raz, a restartLoader był uruchamiany przy drugim i kolejnych przebiegach onResume )
Pamiętaj też, aby przypisać różne identyfikatory każdemu z programów ładujących w ramach działania (co może stanowić problem z fragmentami w ramach tej czynności, jeśli nie jesteś ostrożny z numeracją)
Próbowałem użyć tylko initLoadera ... nie wydawało się działać skutecznie.
Wypróbowano initLoader na onCreate z zerowymi argumentami (dokumentacja twierdzi, że to jest w porządku) i restartLoader (z poprawnymi argumentami) w onResume .... dokumenty są błędne i initLoader zgłasza wyjątek nullpointer.
Próbowano tylko restartLoader ... działa przez chwilę, ale wieje przy zmianie orientacji 5 lub 6 ekranu.
Wypróbowano initLoader w onResume ; znowu działa przez chwilę, a potem dmucha. (w szczególności błąd „Wywołany doRetain, gdy nie został uruchomiony:”… błąd)
Próbowałem wykonać następujące czynności: (fragment z klasy okładki, której identyfikator programu ładującego został przekazany do konstruktora)
/** * start or restart the loader (why bother with 2 separate functions ?) (now I know why) * * @param manager * @param args * @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate */ @Deprecated public void start(LoaderManager manager, Bundle args) { if (manager.getLoader(this.id) == null) { manager.initLoader(this.id, args, this); } else { manager.restartLoader(this.id, args, this); } }
(które znalazłem gdzieś w Stack-Overflow)
Znowu to działało przez chwilę, ale nadal powodowało sporadyczne usterki.
Z tego, co mogę dowiedzieć się podczas debugowania, myślę, że jest coś wspólnego ze stanem instancji zapisywania / przywracania, który wymaga, aby initLoader (/ y) były uruchamiane w części cyklu życia onCreate, jeśli mają przetrwać spin cyklu . ( Mogę się mylić.)
w przypadku Menadżerów, których nie można uruchomić, dopóki wyniki nie wrócą od innego managera lub zadania (tj. nie można ich zainicjować w onCreate ), używam tylko initLoadera . (Może nie mam racji, ale wydaje się, że działa. Te dodatkowe programy ładujące nie są częścią bezpośredniego stanu instancji, więc użycie initLoader może być w tym przypadku poprawne)
Patrząc na diagramy i dokumenty, pomyślałem, że initLoader powinien wejść w onCreate & restartLoader w onRestart for Activities, ale to pozostawia fragmenty używające innego wzorca i nie miałem czasu na zbadanie, czy to faktycznie jest stabilne. Czy ktokolwiek inny może skomentować, czy odniósł sukces z tym wzorcem działań?
źródło
initLoader
użyje ponownie tych samych parametrów, jeśli program ładujący już istnieje. Wraca natychmiast, jeśli stare dane są już załadowane, nawet jeśli wywołasz je z nowymi parametrami. Program ładujący powinien idealnie automatycznie powiadamiać aktywność o nowych danych. Jeśli ekran się obróci,initLoader
zostanie wywołany ponownie, a stare dane zostaną natychmiast wyświetlone.restartLoader
służy do wymuszenia ponownego załadowania i zmiany parametrów. Gdybyś miał utworzyć ekran logowania za pomocą programów ładujących, dzwoniłbyś tylko zarestartLoader
każdym kliknięciem przycisku. (Przycisk może być klikany wiele razy z powodu nieprawidłowych poświadczeń itp.). Dzwonisz tylkoinitLoader
wtedy, gdy przywracasz zapisany stan wystąpienia działania w przypadku obrócenia ekranu podczas logowania.źródło
Jeśli program ładujący już istnieje, restartLoader zatrzyma / anuluje / zniszczy stary, podczas gdy initLoader po prostu zainicjuje go z podanym wywołaniem zwrotnym. Nie mogę się dowiedzieć, co w takich przypadkach robią stare callbacki, ale myślę, że zostaną po prostu porzucone.
Przeskanowałem http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java, ale nie mogę dowiedzieć się, co dokładnie różnica polega na tym, że poza tym metody robią różne rzeczy. Więc powiedziałbym, że użyj initLoader za pierwszym razem i zrestartuj go dla następnych razy, chociaż nie mogę z całą pewnością powiedzieć, co dokładnie zrobi każdy z nich.
źródło
initLoader
w tym przypadku?Program ładujący inicjujący przy pierwszym uruchomieniu używa metody loadInBackground (), przy drugim uruchomieniu zostanie ona pominięta. Zatem moim zdaniem lepszym rozwiązaniem jest:
Loader<?> loa; try { loa = getLoaderManager().getLoader(0); } catch (Exception e) { loa = null; } if (loa == null) { getLoaderManager().initLoader(0, null, this); } else { loa.forceLoad(); }
//////////////////////////////////////////////////// /////////////////////////
protected SimpleCursorAdapter mAdapter; private abstract class SimpleCursorAdapterLoader extends AsyncTaskLoader <Cursor> { public SimpleCursorAdapterLoader(Context context) { super(context); } @Override protected void onStartLoading() { if (takeContentChanged() || mAdapter.isEmpty()) { forceLoad(); } } @Override protected void onStopLoading() { cancelLoad(); } @Override protected void onReset() { super.onReset(); onStopLoading(); } }
Spędziłem dużo czasu, aby znaleźć to rozwiązanie - restartLoader (...) nie działał poprawnie w moim przypadku. Jedyna funkcja forceLoad () pozwala zakończyć poprzedni wątek ładowania bez wywołania zwrotnego (dzięki czemu wszystkie transakcje db zostaną zakończone poprawnie) i ponownie uruchomi nowy wątek. Tak, wymaga to trochę więcej czasu, ale jest bardziej stabilne. Tylko ostatni rozpoczęty wątek odbierze wywołanie zwrotne. Tak więc, jeśli chcesz zrobić testy z przerywaniem transakcji db - witaj, spróbuj restartLoader (...), w przeciwnym razie forceLoad (). Jedyną wygodą restartuLoadera (...) jest dostarczenie nowych danych początkowych, czyli parametrów. I proszę nie zapomnij zniszczyć modułu ładującego w metodzie onDetach () odpowiedniego fragmentu w tym przypadku. Pamiętaj też, że czasami, gdy masz jakąś aktywność i powiedz im: 2 fragmenty z programem ładującym, każdy z jednym działaniem obejmującym - dotrzesz tylko do 2 menedżerów modułu ładującego, więc Activity dzieli swój menedżer modułu ładującego z fragmentami, które są wyświetlane na ekranie jako pierwsze podczas ładowania. Wypróbuj LoaderManager.enableDebugLogging (true); aby zobaczyć szczegóły w każdym konkretnym przypadku.
źródło
getLoader(0)
w sposóbtry { ... } catch (Exception e) { ... }
.