lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Loader

Diperkenalkan di Android 3.0, loader memudahkan pemuatan data asinkron dalam aktivitas atau fragmen. Loader memiliki karakteristik ini:

  • Loader tersedia untuk setiap Activity dan Fragment.
  • Loader menyediakan pemuatan data asinkron.
  • Loader memantau sumber data mereka dan memberikan hasil baru bila materi berubah.
  • Loader secara otomatis menghubungkan kembali ke kursor loader lalu saat dibuat kembali setelah perubahan konfigurasi. Karena itu, loader tidak perlu melakukan kueri ulang datanya.

Rangkuman Loader API

Ada beberapa kelas dan antarmuka yang mungkin dilibatkan dalam menggunakan loader pada aplikasi. Semuanya dirangkum dalam tabel ini:

Kelas/Antarmuka Keterangan
LoaderManager Kelas abstrak yang dikaitkan dengan Activity atau Fragment untuk mengelola satu atau beberapa instance Loader. Ini membantu aplikasi mengelola operasi berjalan lebih lama bersamaan dengan daur hidup Activity atau Fragment; penggunaan paling umumnya adalah dengan CursorLoader, akan tetapi aplikasi bebas menulis loader-nya sendiri untuk memuat tipe data lainnya.

Hanya ada satu LoaderManager per aktivitas atau fragmen. Namun LoaderManager bisa memiliki beberapa loader.
LoaderManager.LoaderCallbacks Antarmuka callback untuk klien berinteraksi dengan LoaderManager. Misalnya, Anda menggunakan metode callback onCreateLoader() untuk membuat loader baru.
Loader Kelas abstrak yang melakukan pemuatan data asinkron. Ini adalah kelas dasar untuk loader. Biasanya Anda akan menggunakan CursorLoader, namun Anda bisa mengimplementasikan subkelas sendiri. Selagi aktif, loader harus memantau sumber datanya dan memberikan hasil baru bila materi berubah.
AsyncTaskLoader Loader abstrak yang menyediakan AsyncTask untuk melakukan pekerjaan.
CursorLoader Subkelas AsyncTaskLoader yang membuat kueri ContentResolver dan mengembalikan Cursor. Kelas ini mengimplementasikan protokol Loader dengan cara standar untuk kueri kursor, yang dibangun pada AsyncTaskLoader untuk melakukan kueri kursor pada thread latar belakang agar tidak memblokir UI aplikasi. Menggunakan loader ini merupakan cara terbaik untuk memuat data secara asinkron dari ContentProvider, sebagai ganti melakukan kueri terkelola melalui fragmen atau API aktivitas.

Kelas dan antarmuka dalam tabel di atas merupakan komponen esensial yang akan Anda gunakan untuk mengimplementasikan loader dalam aplikasi Anda. Anda tidak memerlukan semuanya untuk setiap loader yang dibuat, namun Anda akan selalu memerlukan referensi ke LoaderManager untuk melakukan inisialisasi loader dan implementasi kelas Loader seperti CursorLoader. Bagian berikut ini menunjukkan kepada Anda cara menggunakan kelas dan antarmuka ini dalam aplikasi.

Menggunakan Loader dalam Aplikasi

Bagian ini menjelaskan cara menggunakan loader dalam aplikasi Android. Aplikasi yang menggunakan loader biasanya berisi yang berikut ini:

Memulai Loader

LoaderManager mengelola satu atau beberapa instance Loader dalam Activity atau Fragment. Hanya ada satu LoaderManager per aktivitas atau fragmen.

Anda biasanya melakukan inisialisasi Loader dalam metode onCreate() aktivitas, atau dalam metode onActivityCreated() fragmen. Anda melakukannya dengan cara berikut ini:

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);

Metode initLoader() mengambil parameter berikut:

  • ID unik yang mengidentifikasi loader. Dalam contoh ini, ID-nya adalah 0.
  • Argumen opsional untuk dipasok ke loader pada saat pembuatan (dalam contoh ini null).
  • Implementasi LoaderManager.LoaderCallbacks, yang akan dipanggil LoaderManager untuk melaporkan kejadian loader. Dalam contoh ini, kelas lokal mengimplementasikan antarmuka LoaderManager.LoaderCallbacks, sehingga meneruskan referensi ke dirinya sendiri, this.

Panggilan initLoader() memastikan bahwa loader telah dimulai dan aktif. Ia memiliki dua kemungkinan hasil:

  • Jika loader yang disebutkan oleh ID sudah ada, loader yang dibuat terakhir akan digunakan kembali.
  • Jika loader yang disebutkan oleh ID tidak ada, initLoader() akan memicu metode LoaderManager.LoaderCallbacks onCreateLoader(). Di sinilah Anda mengimplementasikan kode untuk membuat instance dan mengembalikan loader baru. Untuk diskusi selengkapnya, lihat bagian onCreateLoader.

Dalam hal ini, implementasi LoaderManager.LoaderCallbacks yang ditentukan akan dikaitkan dengan loader, dan akan dipanggil bila status loader berubah. Jika saat panggilan ini status pemanggil sudah dimulai, dan loader yang diminta sudah ada dan telah menghasilkan datanya, maka sistem segera memanggil onLoadFinished() (selama initLoader()), sehingga Anda harus siap bila hal ini terjadi. Lihat onLoadFinished untuk diskusi selengkapnya mengenai callback ini

Perhatikan bahwa metode initLoader() mengembalikan Loader yang dibuat, namun Anda tidak perlu menangkap acuan ke sana. LoaderManager mengelola masa hidup loader secara otomatis. LoaderManager memulai dan menghentikan pemuatan jika perlu, dan menjaga status loader dan materi terkaitnya. Seperti yang tersirat di sini, Anda akan jarang berinteraksi dengan loader secara langsung (meskipun misalnya menggunakan metode loader untuk menyempurnakan perilaku loader, lihat contoh LoaderThrottle). Anda paling sering akan menggunakan metode LoaderManager.LoaderCallbacks untuk mengintervensi proses pemuatan saat terjadi kejadian tertentu. Untuk diskusi selengkapnya mengenai topik ini, lihat Menggunakan Callback LoaderManager.

Memulai Ulang Loader

Bila Anda menggunakan initLoader(), seperti ditampilkan di atas, loader yang ada akan digunakan dengan ID yang ditetapkan jika ada. Jika tidak ada, ID akan dibuat. Namun kadang-kadang Anda perlu membuang data lama dan mulai dari awal.

Untuk membuang data lama, gunakan restartLoader(). Misalnya, implementasi SearchView.OnQueryTextListener ini akan memulai ulang loader bila kueri pengguna berubah. Loader perlu dimulai ulang agar dapat menggunakan filter penelusuran yang telah direvisi untuk melakukan kueri baru:

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.
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getLoaderManager().restartLoader(0, null, this);
    return true;
}

Menggunakan Callback LoaderManager

LoaderManager.LoaderCallbacks adalah antarmuka callback yang memungkinkan klien berinteraksi dengan LoaderManager.

Loader, khususnya CursorLoader, diharapkan mempertahankan datanya setelah dihentikan. Ini memungkinkan aplikasi mempertahankan datanya di aktivitas atau metode onStop() fragmen dan onStart(), sehingga bila pengguna kembali ke aplikasi, mereka tidak harus menunggu data dimuat kembali. Anda menggunakan metode LoaderManager.LoaderCallbacks untuk mengetahui waktu membuat loader baru, dan memberi tahu aplikasi kapan berhenti menggunakan data loader.

LoaderManager.LoaderCallbacks berisi metode ini:

  • onLoadFinished() — Dipanggil bila loader yang dibuat sebelumnya selesai dimuat.
  • onLoaderReset() — Dipanggil bila loader yang dibuat sebelumnya sedang disetel ulang, sehingga datanya tidak tersedia.

Metode ini dijelaskan lebih detail dalam bagian berikutnya.

onCreateLoader

Saat Anda mencoba mengakses loader (misalnya, melalui initLoader()), ia akan memeriksa untuk mengetahui adanya loader yang ditetapkan melalui ID. Jika tidak ada, metode LoaderManager.LoaderCallbacks onCreateLoader() akan dipicu. Di sinilah Anda membuat loader baru. Biasanya ini berupa CursorLoader, namun Anda bisa mengimplementasikan subkelas Loader sendiri.

Dalam contoh ini, metode callback onCreateLoader() membuat CursorLoader. Anda harus membangun CursorLoader menggunakan metode konstruktornya, yang memerlukan set informasi lengkap untuk melakukan kueri ke ContentProvider. Khususnya, ia memerlukan:

  • uri — URI untuk materi yang akan diambil.
  • projection — Daftar berisi kolom yang akan dikembalikan. Meneruskan null akan mengembalikan semua kolom, jadi tidak efisien.
  • selection — Filter yang mendeklarasikan baris yang akan dikembalikan, diformat sebagai klausa SQL WHERE (tidak termasuk WHERE itu sendiri). Meneruskan null akan mengembalikan semua baris untuk URI yang diberikan.
  • selectionArgs — Anda dapat menyertakan ?s dalam pilihan, yang akan digantikan dengan nilai dari selectionArgs, agar muncul dalam pilihan. Nilai-nilai akan diikat sebagai String.
  • sortOrder — Cara menyusun baris, diformat sebagai klausa SQL ORDER BY (tidak termasuk ORDER BY itu sendiri). Meneruskan null akan menggunakan urutan sortir default, yang mungkin tidak berurutan.

Misalnya:

 // If non-null, this is the current filter the user has provided.
String mCurFilter;
...
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 (mCurFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(mCurFilter));
    } 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

Metode ini dipanggil bila loader yang dibuat sebelumnya selesai dimuat. Metode ini dijamin dipanggil sebelum pelepasan data terakhir yang disediakan untuk loader ini. Di titik ini Anda harus membuang semua penggunaan data lama (karena akan segera dilepas), namun jangan melepas sendiri data tersebut karena loader memilikinya dan akan menanganinya.

Loader akan melepas data setelah mengetahui bahwa aplikasi tidak lagi menggunakannya. Misalnya, jika data adalah kursor dari CursorLoader, Anda tidak boleh memanggil close() pada dirinya sendiri. Jika kursor ditempatkan dalam CursorAdapter, Anda harus menggunakan metode swapCursor() agar Cursor lama tidak ditutup. Misalnya:

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter; ... 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); }

onLoaderReset

Metode ini dipanggil bila loader yang dibuat sebelumnya sedang disetel ulang, sehingga datanya tidak tersedia. Callback ini memungkinkan Anda mengetahui kapan data akan dilepas sehingga dapat membuang acuannya ke callback.  

Implementasi ini memanggil swapCursor() dengan nilai null:

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...

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);
}

Contoh

Sebagai contoh, inilah implementasi penuh Fragment yang menampilkan ListView berisi hasil kueri terhadap penyedia materi kontak. CursorLoader digunakan untuk mengelola kueri pada penyedia.

Agar aplikasi dapat mengakses kontak pengguna, seperti yang ditampilkan dalam contoh ini, manifesnya harus menyertakan izin READ_CONTACTS.

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 mCurFilter;

    @Override public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(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);

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }

    @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.
        mCurFilter = !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 (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } 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);
    }
}

Contoh Selengkapnya

Ada beberapa contoh berbeda dalam ApiDemos yang mengilustrasikan cara menggunakan loader:

  • LoaderCursor — Versi lengkap dari cuplikan yang ditampilkan di atas.
  • LoaderThrottle — Contoh cara penggunaan throttling untuk mengurangi jumlah kueri dari penyedia materi saat datanya berubah.

Untuk informasi tentang mengunduh dan memasang contoh SDK, lihat Mendapatkan Contoh.