Różnica między initLoader i restartLoader w LoaderManager


129

Jestem kompletnie zagubiony dotyczące różnic pomiędzy initLoaderi restartLoaderfunkcji grupy LoaderManager:

  • Obaj mają ten sam podpis.
  • restartLoader tworzy także moduł ładujący, jeśli nie istnieje („Uruchamia nowy lub restartuje istniejący program ładujący w tym menedżerze”).

Czy istnieje związek między tymi dwiema metodami? Czy dzwonienie restartLoaderzawsze dzwoni initLoader? Czy mogę zadzwonić restartLoaderbez dzwonienia initLoader? Czy można bezpiecznie zadzwonić initLoaderdwa razy, aby odświeżyć dane? Kiedy powinienem użyć jednego z dwóch i dlaczego ?

Odpowiedzi:


202

Aby odpowiedzieć na to pytanie, musisz zagłębić się w LoaderManagerkod. 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:

  1. Program ładujący z identyfikatorem nie istnieje: obie metody utworzą nowy moduł ładujący, więc nie ma różnicy
  2. Program ładujący o identyfikatorze już istnieje: initLoaderzastąpi tylko wywołania zwrotne przekazane jako parametr, ale nie anuluje ani nie zatrzyma programu ładującego. Dla a CursorLoaderoznacza to, że kursor pozostaje otwarty i aktywny (jeśli tak było przed initLoaderwywoł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) {
    // Loader doesn't already exist -> create new one
    info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
   // Loader exists -> only replace callbacks   
   info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}

restartLoader

LoaderInfo info = mLoaders.get(id);
if (info != null) {
    LoaderInfo inactive = mInactiveLoaders.get(id);
    if (inactive != null) {
        // does a lot of stuff to deal with already inactive loaders
    } else {
        // Keep track of the previous instance of this loader so we can destroy
        // it when the new one completes.
        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, initLoaderzastępuje tylko wywołania zwrotne (info.mCallbacks = ...), jednocześnie restartLoaderdezaktywuje 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 initLoaderi kiedy używać restartLoaderi dlaczego warto mieć te dwie metody. initLoadersł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ć, restartLoaderale 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 restartLoaderniepotrzebnie 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 restartLoaderw tym przypadku). Jeśli chcemy wyświetlić tylko otwarte zamówienia, potrzebujemy nowego zapytania i restartLoaderuż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 restartLoaderzawsze dzwoni initLoader?

Nie, nigdy tak nie jest.

Czy mogę zadzwonić restartLoaderbez dzwonienia initLoader?

Tak.

Czy można bezpiecznie zadzwonić initLoaderdwa razy, aby odświeżyć dane?

Można bezpiecznie zadzwonić initLoaderdwa 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 initLoaderprzywrócenie metod wywołania zwrotnego (a restartLoaderjest 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 initLoaderpo zmianie orientacji, onLoadFinishedod 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:

  1. Sam obsługuje zmiany konfiguracji: dotyczy to fragmentów, które używają setRetainInstance (true) lub działania z odpowiednim android:configChangestagiem w manifeście. Te komponenty nie otrzymają wywołania onCreate po np. Obróceniu ekranu, więc pamiętaj o wywołaniu initLoader/restartLoaderinnej 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, ...).
  2. 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.

: +1: Ostatni punkt. Jeśli użyjesz initLoader(i wszystkie wywołania zwrotne zostały zakończone, program ładujący jest bezczynny) po rotacji, nie otrzymasz onLoadFinishedoddzwonienia, ale jeśli użyjesz restartLoadergo?
Blundell,

Błędny. Metoda initLoader wywołuje metodę onLoadFinished () przed zwróceniem (jeśli program ładujący jest uruchomiony i zawiera dane). Dodałem akapit o zmianach konfiguracji, aby wyjaśnić to bardziej szczegółowo.
Emanuel Moecklin,

6
ach, oczywiście, połączenie Twojej odpowiedzi i odpowiedzi @ alexlockwood daje pełny obraz. Wydaje mi się, że odpowiedź dla innych brzmi: użyj initLoader, jeśli twoje zapytanie jest statyczne i uruchom ponownieLoader, jeśli chcesz zmienić zapytanie
Blundell

1
To ładnie to przywołuje: "użyj initLoader, jeśli twoje zapytanie jest statyczne i zrestartujLoader, jeśli chcesz zmienić zapytanie"
Emanuel Moecklin

1
@Mhd. Tahawi, nie zmieniasz callbacków, ustawiasz je tylko tam, gdzie powinny. Po obróceniu ekranu należy je ponownie ustawić, ponieważ Android nie będzie ich trzymał, aby zapobiec wyciekom pamięci. Możesz ustawić je tak, jak chcesz, o ile postępują właściwie.
Emanuel Moecklin

46

Wywołanie, initLoadergdy 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ącego onLoadFinished. Jeśli initLoadermoduł ładujący nie został jeszcze utworzony (na przykład podczas pierwszego uruchomienia działania / fragmentu), wywołanie mówi LoaderManagerowi, aby wywołał w onCreateLoadercelu utworzenia nowego modułu ładującego .

Wywołanie restartLoaderniszczy 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 w onCreateLoadercelu utworzenia nowego modułu ładującego i zainicjowania nowego obciążenia.


Dokumentacja jest również dość jasna:

  • initLoaderzapewnia, ż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.

  • restartLoaderuruchamia 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.


@TomanMoney W mojej odpowiedzi wyjaśniłem, co to oznacza. W jakiej części jesteś zdezorientowany?
Alex Lockwood

właśnie powtórzyłeś dokumentację. Ale dokument nie podaje żadnych wskazówek, gdzie należy zastosować każdą metodę i dlaczego źle jest ją zepsuć. Z mojego doświadczenia wynika, że ​​po prostu wywołanie restartLoader i nigdy nie wywoływanie initLoader działa dobrze. Więc to wciąż jest mylące.
Tom anMoney

3
@TomanMoney Zwykle używasz initLoader()w onCreate()/ 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łanie initLoader()po prostu zwróci stare Loaderzamiast tworzenie nowego. Zwykle używasz, restartLoader()gdy musisz zmienić Loaderzapytanie (np. Chcesz uzyskać przefiltrowane / posortowane dane itp.).
Alex Lockwood,

4
Nadal jestem zdezorientowany decyzją API, aby mieć obie metody, ponieważ mają one ten sam podpis. Dlaczego interfejs API nie mógł być pojedynczą metodą startLoader (), która za każdym razem wykonuje „właściwą rzecz”? Myślę, że to jest ta część, która dezorientuje wielu ludzi.
Tom anMoney

1
@TomanMoney Dokumentacja tutaj mówi: developer.android.com/guide/components/loaders.html . „Po zmianie konfiguracji automatycznie łączą się ponownie z kursorem ostatniego modułu ładującego, gdy są odtwarzane po zmianie konfiguracji. Dzięki temu nie muszą ponownie sprawdzać swoich danych”.
Igor Ganapolsky

16

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)

koło życia


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ń?


/ @ Simon jest w 100% poprawne i to powinna być zaakceptowana odpowiedź. Nie bardzo wierzyłem w jego odpowiedź i spędziłem kilka godzin, próbując znaleźć różne sposoby wykonania tej pracy. Gdy tylko przeniosłem wywołanie initLoader do onCreate, wszystko zaczęło działać. Następnie potrzebujesz flagi jednorazowej, aby uwzględnić czasy wywołania onStart, ale nie onCreate
CjS

2
„Próbowałem tylko restartLoader ... działa przez chwilę, ale wieje przy zmianie orientacji ekranu na 5. lub 6.”. To robi? Po prostu spróbowałem i obróciłem ekran sto razy, ale nie wysadziłem. Jaki wyjątek otrzymujesz?
Tom anMoney

-1 Doceniam wysiłek badawczy związany z tą odpowiedzią, ale większość wyników jest niepoprawna.
Emanuel Moecklin,

1
@IgorGanapolsky prawie wszystko. Jeśli przeczytasz i zrozumiesz moją odpowiedź, zrozumiesz, co robią initLoader i restartLoader i kiedy ich użyć, a także zrozumiesz, dlaczego prawie wszystkie wnioski Simona są błędne. Nie ma związku między cyklem życia fragmentu / aktywności a decyzją, kiedy użyć initLoader / restartLoader (z zastrzeżeniem, które wyjaśniam przy zmianach konfiguracji). Simon wnioskuje na podstawie prób i błędów, że cykl życia jest wskazówką do zrozumienia dwóch metod, ale tak nie jest.
Emanuel Moecklin,

@IgorGanapolsky Nie próbuję reklamować własnej odpowiedzi. Po prostu próbuję pomóc innym programistom i uniemożliwić im wykorzystywanie wyników Simona do ich własnych aplikacji. Gdy zrozumiesz, do czego służą te dwie metody, całość stanie się dość oczywista i prosta do wdrożenia.
Emanuel Moecklin

0

initLoaderuż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, initLoaderzostanie wywołany ponownie, a stare dane zostaną natychmiast wyświetlone.

restartLoadersł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 za restartLoaderkażdym kliknięciem przycisku. (Przycisk może być klikany wiele razy z powodu nieprawidłowych poświadczeń itp.). Dzwonisz tylko initLoaderwtedy, gdy przywracasz zapisany stan wystąpienia działania w przypadku obrócenia ekranu podczas logowania.


-1

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.


A co initLoaderw tym przypadku?
theomega

-1

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.


2
-1 owijania wywołanie getLoader(0)w sposób try { ... } catch (Exception e) { ... }.
Alex Lockwood
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.