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

107

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

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

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

Sealskej
источник
@Alex Lockwood, почему мы используем CursorAdapter без ContentProvider, предложите мне stackoverflow.com/questions/20419278/…
почему мы используем CursorAdapter без ContentProvider, пожалуйста, предложите мне stackoverflow.com/questions/20419278/…

Ответы:

155

Я написал простой 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'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;
    }
}

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

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

Кристиан
источник
13
Нашел хороший пример кода, который использует это - bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo/… - нашел его очень полезным!
Шушу
@Cristian Спасибо за пример. Какая лицензия связана с вашим классом. Как его можно использовать повторно?
codinguser
2
Лицензия - Apache 2.0; Вы можете повторно использовать его где и когда захотите. Сообщите мне, есть ли у вас какие-либо улучшения.
Кристиан
14
Отличный материал! Пользователи должны знать об одном ограничении, которое заключается в том, что у него нет механизма обновления при изменении данных (как это должны делать
загрузчики
1
@Jadeye здесь у вас есть человек: ListLoader и SupportListLoader
Cristian
23

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

Николай Еленков
источник
1
На мой взгляд, это самый простой способ. В своем приложении я создал CursorLoaderloadInBackground
спускаемый элемент
14

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

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

Emmby
источник
2
Однако, чтобы использовать автоматический повторный запрос, вам необходимо использовать один и тот же загрузчик для пользовательского интерфейса, а также для обновлений, ограничивая его использование для фоновых служб.
ge0rg 08
12

Третий вариант - просто переопределить 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 в своей бесконечной мудрости решил сделать свой пакет закрытым. Если вы поместите класс в тот же пакет, что и исходный (или совместимый), вы действительно можете использовать исходный наблюдатель. Наблюдатель - очень легкий объект и больше нигде не используется, так что особой разницы это не имеет.

Тимо Ор
источник
Мое наблюдение при быстром тестировании состоит в том, что registerContentObserver будет вызываться только против курсора, если курсор нацелен на поставщика контента. Вы можете подтвердить / опровергнуть это?
Ник Кэмпион,
1
Это не обязательно должен быть ContentProvider. Но курсор должен быть зарегистрирован в uri уведомления (setNotificationUri), а затем он должен быть уведомлен кем-то (обычно ContentProvider, но может быть любым) путем вызова ContentResolver.notifyChange.
Timo Ohr
4
Да. на вашем CustomLoader loadInBackground() , прежде чем вернуть курсор, скажем, cursor.setNotificationUri(getContext().getContentResolver(), uri);что uri может быть просто из случайной строки, например Uri.parse("content://query_slot1"). Похоже, ему все равно, действительно ли ури существует или нет. И однажды я проделал работу с БД. Скажи, это getContentResolver().notifyChange(uri, null);поможет. Затем я могу создать несколько «слотов URI запроса» в файле contant для приложения с небольшим количеством запросов. Я тестирую вставку записи БД во время выполнения, и кажется, что это работает, но я все еще сомневаюсь, что это хорошая практика. Любое предложение?
Yeung
Я использую этот метод с предложением @Yeung, и все работает, включая автоматическую перезагрузку курсора при обновлении базы данных.
DavidH
не нужен unregisterContentObserver?
GPack
2

Третий вариант, предложенный Тимо Ором, вместе с комментариями Йенга дает самый простой ответ (бритва Оккама). Ниже приведен пример полного класса, который подходит мне. Есть два правила использования этого класса.

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

    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();
  }
Джон Мур
источник