Ładowarki

W Androidzie 9 (poziom interfejsu API 28) moduły ładowania są wycofywane. Zalecaną opcją w przypadku rozwiązywania problemów z wczytywaniem danych podczas obsługi cyklu życia Activity i Fragment jest użycie kombinacji obiektów ViewModel i LiveData. Wyświetl modele, które przetrwają zmiany konfiguracji, takie jak ładowanie, ale używają bez stałego kodu. LiveData umożliwia wczytywanie danych z uwzględnieniem cyklu życia danych, których można używać ponownie w modelach w wielu widokach. Możesz też połączyć funkcje LiveData za pomocą MediatorLiveData. Do obserwowania zmian w danych możesz używać wszystkich dostępnych do obserwacji zapytań, np. z bazy danych typu „Room”.

ViewModel i LiveData są też dostępne w sytuacjach, gdy nie masz dostępu do LoaderManager, na przykład w Service. Korzystanie z tych 2 rozwiązań ułatwia dostęp do danych potrzebnych aplikacji bez konieczności zarządzania cyklem życia UI. Więcej informacji o usłudze LiveData znajdziesz w omówieniu usługi LiveData. Więcej informacji o ViewModel znajdziesz w omówieniu funkcji ViewModel.

Interfejs Loader API umożliwia wczytywanie danych od dostawcy treści lub innego źródła danych do wyświetlenia w interfejsie FragmentActivity lub Fragment.

Bez programów ładujących mogą wystąpić takie problemy:

  • Jeśli pobierasz dane bezpośrednio w aktywności lub fragmencie, użytkownicy mogą cierpieć na brak reakcji z powodu potencjalnie powolnych zapytań z wątku interfejsu.
  • Jeśli pobierasz dane z innego wątku, na przykład za pomocą funkcji AsyncTask, odpowiadasz za zarządzanie zarówno tym wątkiem, jak i wątkiem interfejsu użytkownika za pomocą różnych zdarzeń cyklu życia lub fragmentów, takich jak onDestroy() i zmiany konfiguracji.

Ładowarki rozwiązują te problemy i dodają inne korzyści:

  • Programy ładowania uruchamiają się w oddzielnych wątkach, co zapobiega powolnemu lub niereagowaniu interfejsu użytkownika.
  • Moduły wczytywania upraszczają zarządzanie wątkami, udostępniając metody wywołania zwrotnego w przypadku wystąpienia zdarzeń.
  • Moduły wczytujące są zachowywane i buforują wyniki po wprowadzeniu zmian w konfiguracji, aby zapobiec powielaniu zapytań.
  • Moduły wczytujące mogą wdrożyć obserwatora do monitorowania zmian w bazowym źródle danych. Na przykład CursorLoader automatycznie rejestruje ContentObserver, aby aktywować ponowne załadowanie w przypadku zmiany danych.

Podsumowanie interfejsu Loader API

Jest wiele klas i interfejsów, z których może korzystać wiele modułów ładowania w aplikacji. Zostały one opisane w tej tabeli:

Klasa/interfejs Opis
LoaderManager Klasa abstrakcyjna powiązana z FragmentActivity lub Fragment służącą do zarządzania co najmniej 1 instancją Loader. Na aktywność lub fragment występuje tylko 1 element LoaderManager, ale element LoaderManager może zarządzać wieloma elementami wczytującymi.

Aby uzyskać LoaderManager, wywołaj getSupportLoaderManager() z aktywności lub fragmentu.

Aby rozpocząć wczytywanie danych z modułu wczytywania, wywołaj initLoader() lub restartLoader(). System automatycznie określa, czy moduł ładowania o takim samym identyfikatorze liczby całkowitej już istnieje, i tworzy nowy moduł ładowania lub używa istniejącego.

LoaderManager.LoaderCallbacks Ten interfejs zawiera metody wywołań zwrotnych, które są wywoływane po wystąpieniu zdarzeń wczytywania. Interfejs definiuje 3 metody wywołania zwrotnego:
  • onCreateLoader(int, Bundle): wywoływany, gdy system wymaga utworzenia nowego modułu ładowania. Utwórz w kodzie obiekt Loader i zwróć go do systemu.
  • onLoadFinished(Loader<D>, D): wywoływane po zakończeniu ładowania danych przez program wczytywania. Zazwyczaj dane te są wyświetlane użytkownikowi w kodzie.
  • onLoaderReset(Loader<D>): jest wywoływane podczas resetowania utworzonego wcześniej programu wczytującego, gdy wywołujesz destroyLoader(int) lub gdy działanie lub fragment zostały zniszczone, przez co jego dane są niedostępne. Usuń z kodu wszelkie odwołania do danych modułu ładowania.
Twoja aktywność lub fragment zwykle implementują ten interfejs i są rejestrowane podczas wywoływania initLoader() lub restartLoader().
Loader Roboty wczytują dane. Ta klasa jest abstrakcyjna i służy jako klasa bazowa wszystkich modułów ładowania. Aby uprościć implementację, możesz bezpośrednio podklasę Loader lub użyć jednej z tych wbudowanych podklas:

Z sekcji poniżej dowiesz się, jak używać tych klasy i interfejsów w aplikacji.

Używanie programów ładujących w aplikacji

W tej sekcji opisano, jak korzystać z programów ładowanych w aplikacji na Androida. Aplikacja, która korzysta z modułów wczytujących, zwykle zawiera:

Uruchom ładowanie

LoaderManager zarządza co najmniej 1 instancją Loader w obrębie FragmentActivity lub Fragment. Na aktywność lub fragment występuje tylko 1 element LoaderManager.

Zwykle inicjujesz element Loader w metodzie onCreate() aktywności lub onCreate() fragmentu. Robi się to w następujący sposób:

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() przyjmuje te parametry:

  • Unikalny identyfikator określający program wczytujący. W tym przykładzie identyfikator to 0.
  • Opcjonalne argumenty przekazywane do ładowania podczas konstruowania (w tym przykładzie null).
  • Implementacja LoaderManager.LoaderCallbacks, która wywołuje metodę LoaderManager w celu raportowania zdarzeń ładowania. W tym przykładzie klasa lokalna implementuje interfejs LoaderManager.LoaderCallbacks, więc przekazuje odniesienie do siebie samej – this.

Wywołanie initLoader() gwarantuje, że program wczytywania zostanie zainicjowany i aktywny. Może to wynikać z 2 powodów:

  • Jeśli moduł ładowania określony przez identyfikator już istnieje, ponownie używany jest ostatnio utworzony program wczytujący.
  • Jeśli komponent wczytujący określony przez identyfikator nie istnieje, initLoader() wywołuje metodę LoaderManager.LoaderCallbacks onCreateLoader(). W tym miejscu musisz zaimplementować kod, aby utworzyć instancję i zwrócić nowy moduł ładowania. Więcej dyskusji znajdziesz w sekcji na temat onCreateLoader.

W obu przypadkach dana implementacja LoaderManager.LoaderCallbacks jest powiązana z modułem ładowania i jest wywoływana po zmianie stanu modułu. Jeśli w momencie tego wywołania element wywołujący jest w stanie uruchomienia, a żądany program wczytujący już istnieje i wygenerował swoje dane, system natychmiast, w ramach initLoader(), wywołuje metodę onLoadFinished(). Musisz się na to przygotować. Więcej o tym wywołaniu zwrotnym dowiesz się w sekcji o onLoadFinished.

Metoda initLoader() zwraca utworzony Loader, ale nie musisz przechwytywać do niego odniesienia. Element LoaderManager automatycznie zarządza żywotnością programu wczytującego. W razie potrzeby LoaderManager uruchamia się i przestaje ładować się oraz zachowuje stan modułu wczytywania i powiązanej z nim treści.

Oznacza to, że rzadko wchodzisz w bezpośrednią interakcję z programami wczytującymi dane. Metody LoaderManager.LoaderCallbacks są używane najczęściej do interwencji w procesie wczytywania, gdy występują określone zdarzenia. Więcej informacji na ten temat znajdziesz w sekcji Korzystanie z wywołań zwrotnych LoaderManager.

Ponowne uruchomienie narzędzia

Użycie żądania initLoader(), jak pokazano w poprzedniej sekcji, używa istniejącego modułu wczytywania o określonym identyfikatorze (jeśli istnieje). Jeśli go nie ma, zostanie utworzony. ale czasem chcesz porzucić stare dane i zacząć od nowa.

Aby odrzucić stare dane, użyj restartLoader(). Na przykład taka implementacja SearchView.OnQueryTextListener powoduje ponowne uruchomienie narzędzia wczytującego się po zmianie zapytania użytkownika. Należy ponownie uruchomić program wczytujący, aby mógł użyć zmienionego filtra wyszukiwania do wykonania 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 LoaderManager

LoaderManager.LoaderCallbacks to interfejs wywołania zwrotnego, który umożliwia klientowi interakcję z LoaderManager.

Moduły ładowania, w szczególności CursorLoader, powinny zachowywać swoje dane po zatrzymaniu. Pozwala to aplikacjom na zachowanie danych w metodach onStop() i onStart() aktywności lub fragmentu, dzięki czemu po powrocie użytkownika do aplikacji nie będą musieli czekać na ponowne załadowanie danych.

Metody LoaderManager.LoaderCallbacks pozwalają określić, kiedy należy utworzyć nowy moduł ładowania, i poinformować aplikację, kiedy należy przestać korzystać z jego danych.

LoaderManager.LoaderCallbacks obejmuje te metody:

  • onLoadFinished(): wywoływany po zakończeniu wczytywania wcześniej utworzonego modułu wczytywania.
  • onLoaderReset(): wywoływany, gdy utworzony wcześniej program wczytujący jest resetowany, przez co jego dane są niedostępne.

Metody te zostały szczegółowo opisane w kolejnych sekcjach.

onCreateLoader

Gdy próbujesz uzyskać dostęp do programu wczytującego, np. za pomocą funkcji initLoader(), sprawdza on, czy istnieje moduł ładowania określony przez identyfikator. Jeśli nie, aktywuje metodę LoaderManager.LoaderCallbacks onCreateLoader(). Tutaj utworzysz nowy moduł wczytywania. Zwykle jest to CursorLoader, ale możesz wdrożyć własną podklasę Loader.

W poniższym przykładzie metoda wywołania zwrotnego onCreateLoader() tworzy CursorLoader za pomocą swojej metody konstruktora, która wymaga pełnego zestawu informacji niezbędnych do wykonania zapytania do ContentProvider. W szczególności potrzebne są:

  • uri: identyfikator URI treści, którą chcesz pobrać.
  • projekcja: lista kolumn do zwrócenia, Zaliczenie null zwraca wszystkie kolumny, co jest nieefektywne.
  • zaznaczenie: filtr deklarujący, które wiersze mają zostać zwrócone, w formacie klauzuli SQL WHERE (z wyłączeniem WHERE). Zaliczenie null zwraca wszystkie wiersze z danym identyfikatorem URI.
  • selectionArgs: jeśli w zaznaczeniu uwzględnisz znaki ?s, zostaną one zastąpione wartościami z atrybutu selectionArgs w kolejności, w jakiej występują w zaznaczeniu. Wartości są powiązane jako ciągi znaków.
  • sortOrder: sposób sortowania wierszy w postaci klauzuli SQL ORDER BY (bez samej ORDER BY). Zaliczenie null powoduje użycie domyślnej kolejności sortowania, która może być nieposortowana.

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 to rozwiązanie

Ta metoda jest wywoływana, gdy utworzony wcześniej program wczytujący zakończy wczytywanie. Ta metoda na pewno zostanie wywołana przed opublikowaniem ostatnich danych przekazanych dla tego modułu wczytywania. Na tym etapie usuń wszelkie możliwości korzystania ze starych danych, ponieważ wkrótce zostaną one opublikowane. Nie udostępniaj jednak danych samodzielnie – jest ich właścicielem i zajmuje się nimi.

Moduł ładowania danych udostępnia dane, gdy wie, że aplikacja ich już nie używa. Jeśli na przykład danymi jest kursor z CursorLoader, nie wywołuj w nim funkcji close(). Jeśli kursor jest umieszczony w elemencie CursorAdapter, użyj metody swapCursor(), aby stary obiekt Cursor nie został zamknięty, jak pokazano 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, gdy utworzony wcześniej program wczytujący jest resetowany, przez co jego dane są niedostępne. Wywołanie zwrotne pozwala dowiedzieć się, kiedy dane będą wkrótce udostępniane, i w razie potrzeby usunąć do nich odwołanie.

Ta implementacja wywołuje funkcję 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 przykład pełnej implementacji funkcji Fragment, która wyświetla ListView z wynikami zapytania względem dostawcy treści kontaktów. Używa interfejsu CursorLoader do zarządzania zapytaniem u dostawcy.

W tym przykładzie aplikacja uzyskuje dostęp do kontaktów użytkownika, więc jej plik manifestu musi zawierać uprawnienie 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: