Moduły ładujące zostały wycofane w Androidzie 9 (poziom interfejsu API 28). Zalecana opcja w przypadku
do wczytywania danych przy obsłudze cykli życia Activity
i Fragment
jest użycie
kombinacja obiektów ViewModel
i LiveData
.
Wyświetlanie modeli przeżywa zmiany konfiguracji, takie jak moduły ładowania, ale
prostsza metoda. LiveData
umożliwia wczytywanie z uwzględnieniem cyklu życia danych, które można wykorzystać ponownie
modeli wielu widoków. Możesz też połączyć LiveData
za pomocą:
MediatorLiveData
Wszystkie obserwowalne zapytania, takie jak z
bazy danych sal, która może służyć do obserwowania zmian;
danych.
Usługi ViewModel
i LiveData
są też dostępne, gdy nie masz dostępu
do LoaderManager
, na przykład w
Service
. Zastosowanie dwóch
tandem zapewnia prosty sposób dostępu do danych, których potrzebuje aplikacja, bez konieczności zajmowania się interfejsem użytkownika
cyklu życia usługi. Więcej informacji o funkcji LiveData
znajdziesz tutaj:
LiveData
omówienie. Aby dowiedzieć się więcej o:
ViewModel
, zobacz ViewModel
przegląd.
Interfejs Loader API umożliwia wczytywanie danych z
dostawca treści
lub inne źródło danych do wyświetlenia w tabeli FragmentActivity
lub Fragment
.
Jeśli nie masz żadnych programów ładujących, mogą wystąpić m.in. te problemy:
- Jeśli pobierzesz dane bezpośrednio w aktywności lub we fragmencie, użytkownicy mają brak reakcji z powodu potencjalnie powolnego działania z wątku interfejsu użytkownika.
- Jeśli pobierasz dane z innego wątku, na przykład za pomocą funkcji
AsyncTask
, to Ty odpowiadasz za zarządzanie oraz z wątkiem UI za pomocą różnych zdarzeń cyklu życia działań lub fragmentów, takich jakonDestroy()
i zmiany w konfiguracji.
Moduły wczytujące umożliwiają rozwiązanie tych problemów i zapewniają inne korzyści:
- Moduły ładowania działają w osobnych wątkach, aby zapobiegać powolnym lub nieodpowiadającym działaniu interfejsu.
- Moduły wczytujące upraszczają zarządzanie wątkami, udostępniając metody wywołania zwrotnego, gdy zdarzenia wystąpienia.
- Moduły wczytywania są zachowywane i zapisywane w pamięci podręcznej po zmianie konfiguracji, co uniemożliwia podwójne zapytania.
- Moduły wczytujące mogą wdrożyć obserwatora do monitorowania zmian w bazowym
źródła danych. Na przykład:
CursorLoader
automatycznie rejestrujeContentObserver
, aby uruchomić ponowne załadowanie gdy dane ulegną zmianie.
Podsumowanie interfejsu Loader API
Korzystanie z różnych klas i interfejsów programy ładujące w aplikacji. Ich podsumowanie znajdziesz w tej tabeli:
Klasa/interfejs | Opis |
---|---|
LoaderManager |
Klasa abstrakcyjna powiązana z elementem FragmentActivity lub
Fragment za zarządzanie co najmniej jednym
Loader instancji. Jest tylko jedna
LoaderManager za działanie lub fragment, ale
LoaderManager może zarządzać wieloma modułami wczytującymi.
Aby otrzymać Aby zacząć ładować dane z programu wczytującego, wywołaj jedną
|
LoaderManager.LoaderCallbacks |
Ten interfejs zawiera metody wywołania zwrotnego, które są wywoływane, gdy
zdarzenia ładowania. Interfejs definiuje 3 metody wywołania zwrotnego:
initLoader() lub
restartLoader()
|
Loader |
Moduły wczytujące dane wczytują dane. Ta klasa jest abstrakcyjna i służy
jako klasę bazową wszystkich modułów ładowania. Możesz bezpośrednio podklasę
Loader lub skorzystaj z jednego z poniższych
podklasy, aby uprościć implementację:
|
W sekcjach poniżej dowiesz się, jak używać tych funkcji klas i interfejsów w aplikacji.
Korzystanie z programów wczytujących w aplikacji
W tej sekcji opisujemy, jak korzystać z programów uruchamiających w aplikacji na Androida. An to zwykle:
FragmentActivity
lubFragment
.- Wystąpienie
LoaderManager
. CursorLoader
, aby wczytać dane oparte na:ContentProvider
. Możesz też wdrożyć własną podklasę odLoader
lubAsyncTaskLoader
do wczytywać dane z innego źródła.- Implementacja w języku:
LoaderManager.LoaderCallbacks
. Tutaj tworzysz nowe moduły ładowania i zarządzasz odniesieniami do istniejących z programów uruchamiających. - Sposób wyświetlania danych modułu ładowania, np.
SimpleCursorAdapter
. - źródła danych, np.
ContentProvider
, gdy używasz funkcjiCursorLoader
Uruchom program ładujący
LoaderManager
zarządza co najmniej 1 wystąpieniem Loader
w obrębie FragmentActivity
lub
Fragment
Obowiązuje tylko 1 element LoaderManager
na aktywność lub fragment.
Zazwyczaj
zainicjuj element Loader
w ramach metody onCreate()
aktywności lub tagu
onCreate()
. Ty
wykonaj te czynności:
Kotlin
supportLoaderManager.initLoader(0, null, this)
Java
// Prepare the loader. Either re-connect with an existing one, // or start a new one. getSupportLoaderManager().initLoader(0, null, this);
Metoda initLoader()
pobiera
następujące parametry:
- Unikalny identyfikator identyfikujący program ładujący. W tym przykładzie identyfikator to
0
. - Opcjonalne argumenty do przekazania do narzędzia ładującego na stronie
konstrukcje (w tym przykładzie
null
). - Implementacja
LoaderManager.LoaderCallbacks
, która wywołania funkcjiLoaderManager
do raportowania zdarzeń wczytywania. W tym klasa lokalna implementuje interfejsLoaderManager.LoaderCallbacks
, przekazuje więc odwołanie do siebie samej,this
.
Wywołanie initLoader()
zapewnia, że program wczytujący
jest zainicjowany i aktywny. Może to wynikać z dwóch rezultatów:
- Jeśli istnieje już moduł wczytujący określony przez identyfikator, ostatnio utworzony moduł wczytywania został użyty ponownie.
- Jeśli moduł wczytujący określony przez identyfikator nie istnieje,
initLoader()
uruchamiaLoaderManager.LoaderCallbacks
metodaonCreateLoader()
. Tutaj implementujesz kod, by utworzyć instancję i zwrócić nowy komponent wczytujący. Więcej dyskusji znajdziesz w sekcji na tematonCreateLoader
.
W obu przypadkach LoaderManager.LoaderCallbacks
jest powiązana z modułem wczytującym i jest wywoływana, gdy
zmian stanu modułu wczytywania. Jeśli w trakcie tej rozmowy rozmówca jest w
że żądany moduł ładujący już istnieje i wygenerował
danych, system wywołuje funkcję onLoadFinished()
natychmiast w ciągu initLoader()
. Trzeba się na to przygotować. Więcej informacji o tym wywołaniu zwrotnym znajdziesz w sekcji
onLoadFinished
Metoda initLoader()
zwraca utworzoną Loader
,
ale nie trzeba tworzyć odwołania. Zarządzany przez: LoaderManager
automatyczne wczytywanie. LoaderManager
uruchamia się i zatrzymuje ładowanie w razie potrzeby oraz zachowuje stan modułu ładującego
i powiązanych z nimi treści.
Jak to wskazuje, rzadko wchodzisz w interakcje z programami ładującymi.
bezpośrednio.
Najczęściej używasz metod LoaderManager.LoaderCallbacks
do ingerencji w ładowanie
gdy wystąpią określone zdarzenia. Więcej informacji na ten temat znajdziesz w sekcji Używanie wywołań zwrotnych narzędzia LoaderManager.
Ponowne uruchamianie programu wczytującego
Jeśli użyjesz metody initLoader()
, jako
pokazane w poprzedniej sekcji, wykorzystuje istniejący moduł ładowania o podanym identyfikatorze (jeśli taki istnieje).
Jeśli go nie ma, zostanie utworzony. Czasem chcesz odrzucić stare dane
i zacząć od nowa.
Aby odrzucić stare dane, użyj restartLoader()
. Na przykład:
wdrożenie SearchView.OnQueryTextListener
ponownych uruchomień
gdy zmieni się zapytanie użytkownika. Trzeba uruchomić ponownie program ładujący,
że może użyć zmienionego filtra wyszukiwania do utworzenia nowego zapytania.
Kotlin
fun onQueryTextChanged(newText: String?): Boolean { // Called when the action bar search text has changed. Update // the search filter and restart the loader to do a new query // with this filter. curFilter = if (newText?.isNotEmpty() == true) newText else null supportLoaderManager.restartLoader(0, null, this) return true }
Java
public boolean onQueryTextChanged(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = !TextUtils.isEmpty(newText) ? newText : null; getSupportLoaderManager().restartLoader(0, null, this); return true; }
Używanie wywołań zwrotnych narzędzia LoaderManager
LoaderManager.LoaderCallbacks
to interfejs wywołania zwrotnego
który pozwala klientowi na interakcję z LoaderManager
.
Moduły wczytujące, a zwłaszcza CursorLoader
, powinny
zachowywania swoich danych po zatrzymaniu. Dzięki temu aplikacje mogą
w ramach metod onStop()
i onStart()
aktywności lub fragmentu, aby
gdy użytkownicy wracają do aplikacji, nie muszą czekać, aż dane zostaną
załaduj ponownie.
Korzystasz z metod LoaderManager.LoaderCallbacks
, aby wiedzieć, kiedy należy utworzyć nowy program ładujący, i powiadomić aplikację, kiedy
czas, aby przestać używać danych programu wczytującego.
LoaderManager.LoaderCallbacks
zawiera te
metody:
onCreateLoader()
: tworzy wystąpienie i zwraca nową wartośćLoader
dla podanego identyfikatora.
-
onLoadFinished()
: jest wywoływana, gdy utworzony wcześniej moduł wczytywania zakończy wczytywanie.
onLoaderReset()
: gdy utworzony wcześniej moduł ładujący jest resetowany, przez co jego dane niedostępne.
Metody te zostały szczegółowo opisane w kolejnych sekcjach.
onCreateLoader
Gdy próbujesz uzyskać dostęp do programu wczytującego, na przykład za pomocą initLoader()
, sprawdza on,
istnieje program wczytujący określony przez ten identyfikator. Jeśli tak nie jest, aktywuje metodę LoaderManager.LoaderCallbacks
onCreateLoader()
. Ten
tworzy się nowy moduł ładujący. Zwykle jest to CursorLoader
, ale możesz wdrożyć własną podklasę Loader
.
W poniższym przykładzie onCreateLoader()
wywołania zwrotnego tworzy CursorLoader
przy użyciu metody konstruktora, która
wymaga pełnego zestawu informacji potrzebnych do wykonania zapytania do funkcji ContentProvider
. Konkretnie chodzi o:
- uri: identyfikator URI treści, która ma zostać pobrana.
- odwzorowanie: lista kolumn do zwrócenia. Podania
Funkcja
null
zwraca wszystkie kolumny, co jest nieefektywne. - wybór: filtr określający, które wiersze mają zostać zwrócone;
sformatowana jako klauzula SQL WHERE (bez samej klauzuli WHERE). Podania
Funkcja
null
zwraca wszystkie wiersze dla danego identyfikatora URI. - selectionArgs: jeśli w zaznaczonym miejscu dodasz znaki ?s, zostaną one usunięte są zastępowane wartościami z selectionArgs w kolejności, w jakiej występują w zaznaczania. Wartości są powiązane jako ciągi znaków.
- sortOrder: kolejność wierszy w formacie SQL
Klauzula ORDER BY (z wyłączeniem samego ORDER BY). Wyprzedza:
null
korzysta z domyślnej kolejności sortowania, która może nie być porządkowa.
Kotlin
// If non-null, this is the current filter the user has provided. private var curFilter: String? = null ... override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. val baseUri: Uri = if (curFilter != null) { Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, Uri.encode(curFilter)) } else { ContactsContract.Contacts.CONTENT_URI } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" + "${Contacts.HAS_PHONE_NUMBER}=1) AND (" + "${Contacts.DISPLAY_NAME} != ''))" return (activity as? Context)?.let { context -> CursorLoader( context, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC" ) } ?: throw Exception("Activity cannot be null") }
Java
// If non-null, this is the current filter the user has provided. String curFilter; ... public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (curFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(curFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); }
onLoadFinished
Ta metoda jest wywoływana, gdy wcześniej utworzony program ładujący zakończy ładowanie. Gwarantujemy, że ta metoda zostanie wywołana przed opublikowaniem ostatnich danych który jest dostarczany dla tego programu wczytującego. Na tym etapie usuń wszelkie użycie niż te stare. Nie udostępniaj danych ładunek należy do Ciebie i zajmuje się tym.
Moduł ładowania udostępnia dane, gdy dowie się, że aplikacja nie jest już
za jego pomocą. Jeśli na przykład dane są kursorem z elementu CursorLoader
,
nie dzwoń samodzielnie na numer close()
. Jeśli kursor znajduje się w pozycji
umieszczone w CursorAdapter
, użyj metody swapCursor()
, aby
Stara architektura Cursor
nie jest zamknięta, jak w tym przykładzie:
Kotlin
private lateinit var adapter: SimpleCursorAdapter ... override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) adapter.swapCursor(data) }
Java
// This is the Adapter being used to display the list's data. SimpleCursorAdapter adapter; ... public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) adapter.swapCursor(data); }
onLoaderReset
Ta metoda jest wywoływana podczas resetowania utworzonego wcześniej modułu ładującego, dlatego przez udostępnienie danych na jej temat. Dzięki temu wywołaniu możesz dowiedzieć się, kiedy dane są zostanie zwolniony, więc możesz usunąć swoje odnośniki.
Ta implementacja wymaga
swapCursor()
z wartością null
:
Kotlin
private lateinit var adapter: SimpleCursorAdapter ... override fun onLoaderReset(loader: Loader<Cursor>) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. adapter.swapCursor(null) }
Java
// This is the Adapter being used to display the list's data. SimpleCursorAdapter adapter; ... public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. adapter.swapCursor(null); }
Przykład
Oto pełna implementacja elementu Fragment
, który wyświetla element ListView
zawierający
wyników zapytania względem dostawcy treści kontaktów. Do zarządzania zapytaniem u dostawcy używa on interfejsu CursorLoader
.
Ponieważ ten przykład pochodzi z aplikacji uzyskującej dostęp do kontaktów użytkownika, jej
plik manifestu musi zawierać uprawnienia
READ_CONTACTS
Kotlin
private val CONTACTS_SUMMARY_PROJECTION: Array<String> = arrayOf( Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY ) class CursorLoaderListFragment : ListFragment(), SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. private lateinit var mAdapter: SimpleCursorAdapter // If non-null, this is the current filter the user has provided. private var curFilter: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Prepare the loader. Either re-connect with an existing one, // or start a new one. loaderManager.initLoader(0, null, this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // Give some text to display if there is no data. In a real // application, this would come from a resource. setEmptyText("No phone numbers") // We have a menu item to show in action bar. setHasOptionsMenu(true) // Create an empty adapter we will use to display the loaded data. mAdapter = SimpleCursorAdapter(activity, android.R.layout.simple_list_item_2, null, arrayOf(Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS), intArrayOf(android.R.id.text1, android.R.id.text2), 0 ) listAdapter = mAdapter } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { // Place an action bar item for searching. menu.add("Search").apply { setIcon(android.R.drawable.ic_menu_search) setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) actionView = SearchView(activity).apply { setOnQueryTextListener(this@CursorLoaderListFragment) } } } override fun onQueryTextChange(newText: String?): Boolean { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = if (newText?.isNotEmpty() == true) newText else null loaderManager.restartLoader(0, null, this) return true } override fun onQueryTextSubmit(query: String): Boolean { // Don't care about this. return true } override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: $id") } override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. val baseUri: Uri = if (curFilter != null) { Uri.withAppendedPath(Contacts.CONTENT_URI, Uri.encode(curFilter)) } else { Contacts.CONTENT_URI } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" + "${Contacts.HAS_PHONE_NUMBER}=1) AND (" + "${Contacts.DISPLAY_NAME} != ''))" return (activity as? Context)?.let { context -> CursorLoader( context, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC" ) } ?: throw Exception("Activity cannot be null") } override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data) } override fun onLoaderReset(loader: Loader<Cursor>) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null) } }
Java
public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; // If non-null, this is the current filter the user has provided. String curFilter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // Give some text to display if there is no data. In a real // application, this would come from a resource. setEmptyText("No phone numbers"); // We have a menu item to show in action bar. setHasOptionsMenu(true); // Create an empty adapter we will use to display the loaded data. mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(mAdapter); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); } public boolean onQueryTextChange(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; } @Override public boolean onQueryTextSubmit(String query) { // Don't care about this. return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); } // These are the Contacts rows that we will retrieve. static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, }; public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (curFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(curFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); } public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); } }
Więcej przykładów
Poniższe przykłady pokazują, jak korzystać z programów wczytujących:
- LoaderCursor: pełna wersja poprzedniego fragmentu.
- Pobieranie listy kontaktów:
przewodnik, który używa
CursorLoader
do pobierania od dostawcy kontaktów. - LoaderThrottle: przykład użycia ograniczania w celu zmniejszenia liczby. zapytań wykonywanych przez dostawcę treści w przypadku zmiany danych.