CursorLoader-Verwendung ohne ContentProvider

107

Die Android SDK-Dokumentation besagt, dass die startManagingCursor()Methode verzerrt ist:

Diese Methode ist veraltet. Verwenden Sie stattdessen die neue CursorLoader-Klasse mit LoaderManager. Dies ist auch auf älteren Plattformen über das Android-Kompatibilitätspaket verfügbar. Mit dieser Methode kann die Aktivität den Lebenszyklus des angegebenen Cursors basierend auf dem Lebenszyklus der Aktivität für Sie verwalten. Das heißt, wenn die Aktivität gestoppt wird, wird automatisch disable () für den angegebenen Cursor aufgerufen, und wenn sie später neu gestartet wird, wird Requery () für Sie aufgerufen. Wenn die Aktivität zerstört wird, werden alle verwalteten Cursor automatisch geschlossen. Wenn Sie auf HONEYCOMB oder höher abzielen, sollten Sie stattdessen LoaderManager verwenden, der über getLoaderManager () verfügbar ist.

Also würde ich gerne verwenden CursorLoader. Aber wie kann ich es mit benutzerdefinierten CursorAdapterund ohne verwenden ContentProvider, wenn ich URI im Konstruktor von benötige CursorLoader?

Sealskej
quelle
@ Alex Lockwood, warum wir CursorAdapter ohne ContentProvider verwenden, schlagen Sie mir bitte stackoverflow.com/questions/20419278/…
Warum wir CursorAdapter ohne ContentProvider verwenden, schlagen Sie mir bitte stackoverflow.com/questions/20419278/…

Antworten:

155

Ich habe einen einfachen CursorLoader geschrieben , der keinen Inhaltsanbieter benötigt:

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

Es braucht nur die AsyncTaskLoaderKlasse. Entweder die in Android 3.0 oder höher oder die, die mit dem Kompatibilitätspaket geliefert wird.

Ich habe auch eine geschrieben,ListLoader die mit der kompatibel LoadManagerist und zum Abrufen einer generischen java.util.ListSammlung verwendet wird.

Cristian
quelle
13
Ich habe ein schönes Codebeispiel gefunden, das dies verwendet - bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo/… - fand es sehr nützlich!
Shushu
@Cristian Danke für das Beispiel. Welche Lizenz ist mit Ihrer Klasse verbunden? Wie kann es wiederverwendet werden?
Codinguser
2
Lizenz ist Apache 2.0; Sie können es wiederverwenden, wo und wann immer Sie möchten. Lassen Sie mich wissen, wenn Sie Verbesserungen haben.
Cristian
14
Tolles Zeug! Benutzer sollten sich einer Einschränkung bewusst sein, nämlich, dass es keinen Mechanismus zum Aktualisieren von Datenänderungen gibt (wie es
Loader
1
@ Jadeye hier haben Sie Mann: ListLoader und SupportListLoader
Cristian
23

Schreiben Sie Ihren eigenen Loader, der Ihre Datenbankklasse anstelle eines Inhaltsanbieters verwendet. Am einfachsten ist es, die Quelle der CursorLoaderKlasse aus der Kompatibilitätsbibliothek zu entnehmen und Anbieterabfragen durch Abfragen an Ihre eigene DB-Hilfsklasse zu ersetzen.

Nikolay Elenkov
quelle
1
Dies ist meiner Meinung nach der einfachste Weg. In meiner App habe ich einen CursorLoaderNachkommen erstellt, um einen SQLite-Cursor zu verwalten. Ausgehend vom Konstruktor musste ich nur die loadInBackgroundMethode überschreiben , um die Anbieterabfrage durch meine
Cursorabfrage
14

Der SimpleCursorLoader ist eine einfache Lösung, unterstützt jedoch nicht die Aktualisierung des Loaders, wenn sich die Daten ändern. CommonsWare verfügt über eine Loaderex-Bibliothek, die einen SQLiteCursorLoader hinzufügt und die erneute Abfrage von Datenänderungen unterstützt.

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

emmby
quelle
2
Um die automatische Abfrage nutzen zu können, müssen Sie jedoch denselben Loader sowohl für die Benutzeroberfläche als auch für die Updates verwenden, wodurch die Verwendbarkeit für Hintergrunddienste eingeschränkt wird.
Ge0rg
12

Eine dritte Möglichkeit wäre, einfach zu überschreiben 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;
    }
};

Dadurch wird auch sichergestellt, dass der Cursor erneut abgefragt wird, wenn sich die Datenbank ändert.

Einzige Einschränkung: Sie müssen einen anderen Beobachter definieren, da Google in seiner unendlichen Weisheit beschlossen hat, sein Paket privat zu machen. Wenn Sie die Klasse in dasselbe Paket wie die ursprüngliche (oder die kompatible) Klasse einfügen, können Sie tatsächlich den ursprünglichen Beobachter verwenden. Der Beobachter ist ein sehr leichtes Objekt und wird nirgendwo anders verwendet, daher macht dies keinen großen Unterschied.

Timo Ohr
quelle
Meine Beobachtung beim Schnelltest ist, dass registerContentObserver nur gegen den Cursor aufgerufen wird, wenn der Cursor auf einen Inhaltsanbieter gerichtet ist. Können Sie dies bestätigen / ablehnen?
Nick Campion
1
Es muss nicht unbedingt ein ContentProvider sein. Der Cursor muss jedoch bei einer Benachrichtigungs-URL (setNotificationUri) registriert sein und dann von jemandem (normalerweise einem ContentProvider, kann aber alles sein) durch Aufrufen von ContentResolver.notifyChange benachrichtigt werden.
Timo Ohr
4
Ja. Sagen Sie auf Ihrem CustomLoader loadInBackground() , bevor Sie den Cursor zurückgeben, dass cursor.setNotificationUri(getContext().getContentResolver(), uri);die URL möglicherweise nur aus zufälligen Zeichenfolgen besteht Uri.parse("content://query_slot1"). Es scheint mir egal zu sein, ob der Uri wirklich existiert oder nicht. Und sobald ich die DB operiert habe. Say getContentResolver().notifyChange(uri, null);würde den Trick machen. Dann kann ich einige "Abfrage-Uri-Slot" in einer Contant-Datei für App mit einer geringen Anzahl von Abfragen erstellen. Ich teste das Einfügen des DB-Datensatzes zur Laufzeit und es scheint zu funktionieren, aber ich bezweifle immer noch, dass es eine gute Praxis ist. Irgendein Vorschlag?
Yeung
Ich verwende diese Methode mit dem Vorschlag von @Yeung und alles funktioniert, einschließlich des automatischen Neuladens des Cursors bei der Datenbankaktualisierung.
DavidH
Benötigt es keinen unregisterContentObserver?
GPack
2

Die dritte von Timo Ohr vorgeschlagene Option liefert zusammen mit den Kommentaren von Yeung die einfachste Antwort (Occams Rasiermesser). Unten finden Sie ein Beispiel für eine vollständige Klasse, die für mich funktioniert. Es gibt zwei Regeln für die Verwendung dieser Klasse.

  1. Erweitern Sie diese abstrakte Klasse und implementieren Sie die Methoden getCursor () und getContentUri ().
  2. Stellen Sie sicher, dass Sie jedes Mal, wenn sich die zugrunde liegende Datenbank ändert (z. B. nach dem Einfügen oder Löschen), aufrufen

    getContentResolver().notifyChange(myUri, null);

    Dabei ist myUri dasselbe, das Sie von Ihrer Implementierung der Methode getContentUri () zurückgegeben haben.

Hier ist der Code für die Klasse, die ich verwendet habe:

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
quelle