Использование CursorLoader без ContentProvider

Документация SDK для Android говорит, что метод startManagingCursor() не используется:

Этот метод устарел. Вместо этого используйте новый класс CursorLoader с LoaderManager; это также доступно на старых платформах через пакет совместимости с Android. Этот метод позволяет активности позаботиться о том, чтобы управлять данным жизненным циклом Cursor для вас на основе жизненного цикла деятельности. То есть, когда действие остановлено, он автоматически вызывает деактивировать() на данном курсоре, а когда он будет перезапущен позже, он вызовет для вас запрос(). Когда действие будет уничтожено, все управляемые курсоры будут автоматически закрыты. Если вы планируете использовать HONEYCOMB или более позднюю версию, вместо этого вместо этого используйте LoaderManager, доступный через getLoaderManager()

Поэтому я хотел бы использовать CursorLoader. Но как я могу использовать его с пользовательскими CursorAdapter и без ContentProvider, когда мне нужно URI в конструкторе CursorLoader?

Ответ 1

Я написал простой CursorLoader, которому не нужен поставщик контента:

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

Ему нужен только класс AsyncTaskLoader. Либо тот в Android 3.0 или выше, либо тот, который поставляется с пакетом совместимости.

Я также написал a ListLoader, который совместим с LoadManager и используется для извлечения общей коллекции java.util.List.

Ответ 2

Напишите свой собственный загрузчик, который использует ваш класс базы данных вместо поставщика контента. Самый простой способ - просто взять источник класса CursorLoader из библиотеки совместимости и заменить запросы поставщика запросами на свой собственный класс-помощник db.

Ответ 3

SimpleCursorLoader - это простое решение, однако оно не поддерживает обновление загрузчика при изменении данных. CommonsWare имеет библиотеку loaderex, которая добавляет SQLiteCursorLoader и поддерживает повторный запрос об изменениях данных.

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

Ответ 4

Третий вариант - просто переопределить 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;
    }
};

Это также позаботится о повторном запросе вашего курсора при изменении базы данных.

Только оговорка: вам нужно будет определить другого наблюдателя, так как Google в нем бесконечной мудрости решил сделать свой пакет приватным. Если вы поместите класс в тот же пакет, что и исходный (или совместимый), вы можете фактически использовать оригинального наблюдателя. Наблюдатель - очень легкий объект и нигде не используется, поэтому это не имеет большого значения.

Ответ 5

Третий вариант, предложенный Timo Ohr, вместе с комментариями Юнга, дает самый простой ответ (бритва Оккама). Ниже приведен пример полного класса, который работает для меня. Существует два правила использования этого класса.

  • Расширьте этот абстрактный класс и реализуйте методы getCursor() и getContentUri().
  • В любое время, когда базовая база данных изменяется (например, после вставки или удаления), обязательно вызывайте

    getContentResolver().notifyChange(myUri, null);
    

    где myUri является тем же, который был возвращен из вашей реализации метода getContentUri().

Вот код для класса, который я использовал:

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