Використання CursorLoader без ContentProvider


107

Документація Android SDK говорить про те, що startManagingCursor()метод знешкоджено:

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

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


@Alex Локвуд , чому ми використовуємо 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 Дякую за приклад. Яка ліцензія пов’язана з вашим класом. Як її можна повторно використовувати?
кодуваннякористувач

2
Ліцензія - Apache 2.0; ви можете повторно використовувати його там, де / коли хочете. Повідомте мене, чи є якісь покращення.
Крістіан

14
Чудові речі! Користувачі повинні пам’ятати про одне обмеження, яке полягає в тому, що воно не має механізму оновлення змін даних (як це мають робити навантажувачі)
emmby

1
@Jadeye тут у вас є людина: ListLoader і SupportListLoader
Cristian

23

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


1
На мій погляд, це найпростіший спосіб. У своєму додатку я створив CursorLoaderdescendat для керування курсором SQLite, окрім конструктора, мені потрібно було лише замінити loadInBackgroundметод заміни запиту постачальника на мій запит курсора
Jose_GD

14

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

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


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

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. Але курсор повинен бути зареєстрований урі сповіщення (setNotificationUri), і його потрібно повідомити хтось (як правило, ContentProvider, але може бути чим завгодно), зателефонувавши ContentResolver.notifyChange.
Тімо Охр

4
Так. на ваш CustomLoader - х loadInBackground() , перш ніж повернути курсор, скажімо , cursor.setNotificationUri(getContext().getContentResolver(), uri);УІР може просто від випадкової рядки , як Uri.parse("content://query_slot1"). Здається, це не байдуже, урі насправді існують чи ні. І одного разу я зробив операцію над БД. Скажіть, getContentResolver().notifyChange(uri, null);це зробить трюк. Тоді я можу створити кілька «слотів для запиту урі» у файлі, що вміщує програму, для програми з невеликою кількістю запитів. Я тестую вставлення запису БД під час виконання, і, здається, це працює, але я все ще сумніваюся, що це хороша практика. Будь-яка пропозиція?
Yeung

Я використовую цей метод із пропозицією @Yeung, і все працює, включаючи автоматичне перезавантаження курсору при оновленні бази даних.
DavidH

не потрібен це будь-який реєстраторContentObserver?
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();
  }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.