Utilizzo di CursorLoader senza ContentProvider


107

La documentazione di Android SDK dice che il startManagingCursor()metodo è obsoleto:

Questo metodo è deprecato. Utilizza invece la nuova classe CursorLoader con LoaderManager; questo è disponibile anche su piattaforme precedenti tramite il pacchetto di compatibilità Android. Questo metodo consente all'attività di occuparsi della gestione del ciclo di vita del Cursore in base al ciclo di vita dell'attività. Cioè, quando l'attività viene interrotta chiamerà automaticamente disableate () sul Cursor specificato, e quando viene riavviato in seguito chiamerà requery () per te. Quando l'attività viene distrutta, tutti i cursori gestiti verranno chiusi automaticamente. Se stai prendendo di mira HONEYCOMB o versioni successive, considera invece di utilizzare LoaderManager, disponibile tramite getLoaderManager ()

Quindi vorrei usare CursorLoader. Ma come posso usarlo con custom CursorAdaptere senza ContentProvider, quando ho bisogno dell'URI nel costruttore di CursorLoader?


@Alex Lockwood perché stiamo usando CursorAdapter senza ContentProvider prega di suggerire me stackoverflow.com/questions/20419278/...~~V~~singular~~1st

perché stiamo usando CursorAdapter senza ContentProvider prega di suggerire me stackoverflow.com/questions/20419278/...~~V~~singular~~1st

Risposte:


155

Ho scritto un semplice CursorLoader che non necessita di un provider di contenuti:

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

Ha solo bisogno della AsyncTaskLoaderclasse. O quello in Android 3.0 o versioni successive o quello fornito con il pacchetto di compatibilità.

Ho anche scritto unListLoader che è compatibile con LoadManagere viene utilizzato per recuperare una java.util.Listraccolta generica .


13
Ho trovato un bell'esempio di codice che usa questo - bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo/… - l' ho trovato molto utile!
Shushu

@Cristian Grazie per l'esempio. Qual è la licenza associata alla tua classe. Come può essere riutilizzato?
codinguser

2
La licenza è Apache 2.0; puoi riutilizzarlo dove / quando vuoi. Fammi sapere se hai miglioramenti.
Cristian

14
Roba fantastica! Gli utenti dovrebbero essere consapevoli di una limitazione, ovvero che non ha alcun meccanismo per aggiornare le modifiche ai dati (come dovrebbero fare i
caricatori

1
@Jadeye qui hai uomo: ListLoader e SupportListLoader
Cristian

23

Scrivi il tuo caricatore che utilizza la tua classe di database invece di un fornitore di contenuti. Il modo più semplice è semplicemente prendere l'origine della CursorLoaderclasse dalla libreria di compatibilità e sostituire le query del provider con le query alla tua classe helper db.


1
Questo è il modo più semplice secondo me. Nella mia app ho creato un CursorLoaderdescendat per gestire un cursore SQLite, a parte il costruttore avevo solo bisogno di sovrascrivere il loadInBackgroundmetodo per sostituire la query del provider con la mia query del cursore
Jose_GD

14

SimpleCursorLoader è una soluzione semplice, tuttavia non supporta l'aggiornamento del caricatore quando i dati cambiano. CommonsWare ha una libreria loaderex che aggiunge un SQLiteCursorLoader e supporta la nuova query sulle modifiche ai dati.

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


2
Tuttavia, per utilizzare la nuova query automatica, è necessario utilizzare lo stesso caricatore per l'interfaccia utente e per gli aggiornamenti, limitandone l'usabilità per i servizi in background.
ge0rg

12

Una terza opzione sarebbe semplicemente sovrascrivere 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;
    }
};

Ciò si occuperà anche di interrogare nuovamente il cursore quando il database cambia.

Unico avvertimento: dovrai definire un altro osservatore, poiché Google nella sua infinita saggezza ha deciso di rendere privato il loro pacchetto. Se metti la classe nello stesso pacchetto di quello originale (o di quello compatibile) puoi effettivamente usare l'osservatore originale. L'osservatore è un oggetto molto leggero e non viene utilizzato da nessun'altra parte, quindi questo non fa molta differenza.


La mia osservazione nei test rapidi è che registerContentObserver verrà chiamato solo contro il cursore se il cursore è mirato a un fornitore di contenuti. Puoi confermare / negare questo?
Nick Campion

1
Non deve essere necessariamente un ContentProvider. Ma il cursore deve essere registrato in un uri di notifica (setNotificationUri) e quindi deve essere notificato da qualcuno (di solito un ContentProvider, ma può essere qualsiasi cosa) chiamando ContentResolver.notifyChange.
Timo Ohr

4
Si. sul tuo CustomLoader loadInBackground() , prima di restituire il cursore, dì che cursor.setNotificationUri(getContext().getContentResolver(), uri);l'uri potrebbe provenire da una stringa casuale come Uri.parse("content://query_slot1"). Sembra che non gli importi che gli uri esistano davvero o no. E una volta eseguita l'operazione su DB. Say getContentResolver().notifyChange(uri, null);farebbe il trucco. Quindi posso creare alcuni "slot uri di query" in un file continuo per app con un numero ridotto di query. Provo a inserire il record DB in runtime e sembra funzionare ma dubito ancora che sia una buona pratica. Qualche suggerimento?
Yeung

Sto usando questo metodo con il suggerimento di @Yeung e tutto funziona, incluso il ricaricamento automatico del cursore durante l'aggiornamento del database.
DavidH

non serve unregisterContentObserver?
GPack

2

La terza opzione proposta da Timo Ohr, insieme ai commenti di Yeung, forniscono la risposta più semplice (il rasoio di Occam). Di seguito è riportato un esempio di una classe completa che funziona per me. Ci sono due regole per usare questa classe.

  1. Estendi questa classe astratta e implementa i metodi getCursor () e getContentUri ().
  2. Ogni volta che il database sottostante cambia (ad esempio, dopo un inserimento o una cancellazione), assicurati di chiamare

    getContentResolver().notifyChange(myUri, null);

    dove myUri è lo stesso restituito dall'implementazione del metodo getContentUri ().

Ecco il codice per la classe che ho usato:

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();
  }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.