Penggunaan CursorLoader tanpa ContentProvider

107

Dokumentasi Android SDK mengatakan bahwa startManagingCursor()metode tersebut dihentikan:

Metode ini tidak digunakan lagi. Gunakan kelas CursorLoader baru dengan LoaderManager sebagai gantinya; ini juga tersedia di platform lama melalui paket kompatibilitas Android. Metode ini memungkinkan aktivitas untuk mengurus pengelolaan siklus hidup Cursor yang diberikan untuk Anda berdasarkan siklus proses aktivitas. Artinya, saat aktivitas dihentikan, secara otomatis akan memanggil deactivate () pada Cursor yang diberikan, dan saat aktivitas itu dimulai ulang, ia akan memanggil requery () untuk Anda. Saat aktivitas dimusnahkan, semua Cursor yang dikelola akan ditutup secara otomatis. Jika Anda menargetkan HONEYCOMB atau yang lebih baru, pertimbangkan untuk menggunakan LoaderManager sebagai gantinya, tersedia melalui getLoaderManager ()

Jadi saya ingin menggunakan CursorLoader. Tapi bagaimana saya bisa menggunakannya dengan custom CursorAdapterdan tanpa ContentProvider, ketika saya membutuhkan URI dalam konstruktor CursorLoader?

sealskej
sumber
@Alex Lockwood mengapa kami menggunakan CursorAdapter tanpa ContentProvider, harap sarankan saya stackoverflow.com/questions/20419278/…
mengapa kami menggunakan CursorAdapter tanpa ContentProvider, harap sarankan saya stackoverflow.com/questions/20419278/…

Jawaban:

155

Saya menulis CursorLoader sederhana yang tidak membutuhkan penyedia konten:

import android.content.Context;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;

/**
 * Used to write apps that run on platforms prior to Android 3.0. When running
 * on Android 3.0 or above, this implementation is still used; it does not try
 * to switch to the framework's implementation. See the framework SDK
 * documentation for a class overview.
 *
 * This was based on the CursorLoader class
 */
public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
    private Cursor mCursor;

    public SimpleCursorLoader(Context context) {
        super(context);
    }

    /* Runs on a worker thread */
    @Override
    public abstract Cursor loadInBackground();

    /* Runs on the UI thread */
    @Override
    public void deliverResult(Cursor cursor) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            if (cursor != null) {
                cursor.close();
            }
            return;
        }
        Cursor oldCursor = mCursor;
        mCursor = cursor;

        if (isStarted()) {
            super.deliverResult(cursor);
        }

        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
            oldCursor.close();
        }
    }

    /**
     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
     * will be called on the UI thread. If a previous load has been completed and is still valid
     * the result may be passed to the callbacks immediately.
     * <p/>
     * Must be called from the UI thread
     */
    @Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    /**
     * Must be called from the UI thread
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        if (mCursor != null && !mCursor.isClosed()) {
            mCursor.close();
        }
        mCursor = null;
    }
}

Itu hanya membutuhkan AsyncTaskLoaderkelas. Baik yang menggunakan Android 3.0 atau lebih tinggi, atau yang dilengkapi dengan paket kompatibilitas.

Saya juga menulisListLoader yang kompatibel dengan LoadManagerdan digunakan untuk mengambil java.util.Listkoleksi generik .

Cristian
sumber
13
Menemukan contoh kode bagus yang menggunakan ini - bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo/… - merasa sangat berguna!
Shushu
@ Christian Terima kasih untuk contohnya. Lisensi apa yang terkait dengan kelas Anda. Bagaimana itu bisa digunakan kembali?
codinguser
2
Lisensi adalah Apache 2.0; Anda dapat menggunakannya kembali di mana / kapan pun Anda mau. Beri tahu saya jika Anda memiliki peningkatan.
Cristian
14
Barang bagus! Pengguna harus menyadari satu batasan, yaitu tidak ada mekanisme untuk menyegarkan perubahan data (seperti yang seharusnya dilakukan Loader)
emmby
1
@Jadeye di sini Anda memiliki pria: ListLoader dan SupportListLoader
Cristian
23

Tulis loader Anda sendiri yang menggunakan kelas database Anda, bukan penyedia konten. Cara termudah adalah dengan mengambil sumber CursorLoaderkelas dari pustaka kompatibilitas, dan mengganti kueri penyedia dengan kueri ke kelas helper db Anda sendiri.

Nikolay Elenkov
sumber
1
Ini cara termudah menurut saya. Di aplikasi saya, saya membuat CursorLoaderdescendat untuk mengelola kursor SQLite, selain dari konstruktor, saya hanya perlu mengganti loadInBackgroundmetode untuk mengganti kueri penyedia dengan kueri kursor saya
Jose_GD
14

SimpleCursorLoader adalah solusi sederhana, namun tidak mendukung pembaruan loader saat datanya berubah. CommonsWare memiliki pustaka loaderex yang menambahkan SQLiteCursorLoader dan mendukung kueri ulang pada perubahan data.

https://github.com/commonsguy/cwac-loaderex

emmby
sumber
2
Namun, untuk menggunakan kueri ulang otomatis, Anda perlu menggunakan pemuat yang sama untuk UI serta untuk pembaruan, membatasi kegunaannya untuk layanan latar belakang.
ge0rg
12

Opsi ketiga adalah dengan mengganti loadInBackground:

public class CustomCursorLoader extends CursorLoader {
    private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();

    @Override
    public Cursor loadInBackground() {
        Cursor cursor = ... // get your cursor from wherever you like

        if (cursor != null) {
            // Ensure the cursor window is filled
            cursor.getCount();
            cursor.registerContentObserver(mObserver);
        }

        return cursor;
    }
};

Ini juga akan menangani kueri ulang kursor Anda saat database berubah.

Satu-satunya peringatan: Anda harus menentukan pengamat lain, karena Google dengan kebijaksanaannya yang tak terbatas memutuskan untuk menjadikan paket mereka pribadi. Jika Anda memasukkan kelas ke dalam paket yang sama dengan yang asli (atau yang compat), Anda sebenarnya dapat menggunakan pengamat asli. Pengamat adalah objek yang sangat ringan dan tidak digunakan di tempat lain, jadi ini tidak membuat banyak perbedaan.

Timo Ohr
sumber
Pengamatan saya dalam pengujian cepat adalah bahwa registerContentObserver hanya akan dipanggil terhadap kursor jika kursor ditargetkan ke Penyedia Konten. Bisakah Anda mengkonfirmasi / menyangkal ini?
Nick Campion
1
Ini tidak harus berupa ContentProvider. Tapi kursor perlu didaftarkan ke uri notifikasi (setNotificationUri), dan kemudian perlu diberi tahu oleh seseorang (biasanya ContentProvider, tapi bisa apa saja) dengan memanggil ContentResolver.notifyChange.
Timo Ohr
4
Ya. pada CustomLoader Anda loadInBackground() , sebelum mengembalikan kursor, katakan cursor.setNotificationUri(getContext().getContentResolver(), uri);uri mungkin hanya dari String acak seperti Uri.parse("content://query_slot1"). Sepertinya tidak peduli uri benar-benar ada atau tidak. Dan setelah saya melakukan operasi pada DB. Katakan getContentResolver().notifyChange(uri, null);akan melakukan triknya. Kemudian saya dapat membuat beberapa "kueri uri slot" di file contant untuk aplikasi dengan sejumlah kecil kueri. Saya menguji memasukkan catatan DB dalam runtime dan tampaknya berfungsi tetapi saya masih ragu itu adalah praktik yang baik. Ada saran?
Yeung
Saya menggunakan metode ini dengan saran @Yeung dan semuanya berfungsi, termasuk memuat ulang otomatis kursor pada pembaruan basis data.
DavidH
tidak membutuhkannya unregisterContentObserver?
GPack
2

Pilihan ketiga yang diajukan oleh Timo Ohr, bersama dengan komentar Yeung, memberikan jawaban paling sederhana (Occam's razor). Di bawah ini adalah contoh kelas lengkap yang cocok untuk saya. Ada dua aturan untuk menggunakan kelas ini.

  1. Perluas kelas abstrak ini dan implementasikan metode getCursor () dan getContentUri ().
  2. Setiap kali database yang mendasarinya berubah (misalnya, setelah menyisipkan atau menghapus), pastikan untuk memanggil

    getContentResolver().notifyChange(myUri, null);

    di mana myUri sama dengan yang dikembalikan dari implementasi metode getContentUri ().

Berikut kode kelas yang saya gunakan:

package com.example.project;

import android.content.Context;
import android.database.Cursor;
import android.content.CursorLoader;
import android.content.Loader;

public abstract class AbstractCustomCursorLoader extends CursorLoader
  {
    private final Loader.ForceLoadContentObserver mObserver = new Loader.ForceLoadContentObserver();

    public AbstractCustomCursorLoader(Context context)
      {
        super(context);
      }

    @Override
    public Cursor loadInBackground()
      {
        Cursor cursor = getCursor();

        if (cursor != null)
          {
            // Ensure the cursor window is filled
            cursor.getCount();
            cursor.registerContentObserver(mObserver);
          }

        cursor.setNotificationUri(getContext().getContentResolver(), getContentUri());
        return cursor;
      }

    protected abstract Cursor getCursor();
    protected abstract Uri getContentUri();
  }
John Moore
sumber