Utilisation de CursorLoader sans ContentProvider

107

La documentation du SDK Android indique que cette startManagingCursor()méthode est obsolète:

Cette méthode est obsolète. Utilisez plutôt la nouvelle classe CursorLoader avec LoaderManager; ceci est également disponible sur les anciennes plates-formes via le package de compatibilité Android. Cette méthode permet à l'activité de prendre en charge la gestion du cycle de vie du curseur donné pour vous en fonction du cycle de vie de l'activité. Autrement dit, lorsque l'activité est arrêtée, elle appellera automatiquement deactivate () sur le curseur donné, et quand elle sera redémarrée plus tard, elle appellera requery () pour vous. Lorsque l'activité est détruite, tous les curseurs gérés seront fermés automatiquement. Si vous ciblez HONEYCOMB ou une version ultérieure, envisagez plutôt d'utiliser LoaderManager, disponible via getLoaderManager ()

Je voudrais donc utiliser CursorLoader. Mais comment puis-je l'utiliser avec personnalisé CursorAdapteret sans ContentProvider, quand j'ai besoin d'URI dans le constructeur de CursorLoader?

sealskej
la source
@Alex Lockwood pourquoi nous utilisons CursorAdapter sans ContentProvider, veuillez me suggérer stackoverflow.com/questions/20419278
pourquoi nous utilisons CursorAdapter sans ContentProvider, veuillez me suggérer stackoverflow.com/questions/20419278/…

Réponses:

155

J'ai écrit un simple CursorLoader qui n'a pas besoin d'un fournisseur de contenu:

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

Il n'a besoin que de la AsyncTaskLoaderclasse. Soit celui d'Android 3.0 ou supérieur, soit celui fourni avec le package de compatibilité.

J'ai également écrit unListLoader qui est compatible avec le LoadManageret est utilisé pour récupérer une java.util.Listcollection générique .

Cristian
la source
13
J'ai trouvé un bel exemple de code qui utilise ceci - bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo / ... - l'a trouvé très utile!
Shushu
@Cristian Merci pour l'exemple. Quelle est la licence associée à votre classe. Comment peut-il être réutilisé?
codinguser
2
La licence est Apache 2.0; vous pouvez le réutiliser où / quand vous le souhaitez. Faites-moi savoir si vous avez des améliorations.
Cristian
14
Super truc! Les utilisateurs doivent être conscients d'une limitation, à savoir qu'il n'a pas de mécanisme pour actualiser les modifications de données (comme les chargeurs sont censés le faire)
emmby
1
@Jadeye ici vous avez man: ListLoader et SupportListLoader
Cristian
23

Écrivez votre propre chargeur qui utilise votre classe de base de données au lieu d'un fournisseur de contenu. Le moyen le plus simple consiste simplement à extraire la source de la CursorLoaderclasse de la bibliothèque de compatibilité et à remplacer les requêtes de fournisseur par des requêtes dans votre propre classe d'assistance db.

Nikolay Elenkov
la source
1
C'est le moyen le plus simple à mon avis. Dans mon application, j'ai créé un CursorLoaderdescendant pour gérer un curseur SQLite, à part le constructeur, je n'avais besoin que de remplacer la loadInBackgroundméthode pour remplacer la requête du fournisseur par ma requête de curseur
Jose_GD
14

Le SimpleCursorLoader est une solution simple, mais il ne prend pas en charge la mise à jour du chargeur lorsque les données changent. CommonsWare a une bibliothèque loaderex qui ajoute un SQLiteCursorLoader et prend en charge la nouvelle requête sur les modifications de données.

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

emmby
la source
2
Cependant, pour utiliser la nouvelle requête automatique, vous devez utiliser le même chargeur pour l'interface utilisateur ainsi que pour les mises à jour, ce qui limite son utilisation pour les services d'arrière-plan.
ge0rg
12

Une troisième option consisterait simplement à remplacer 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;
    }
};

Cela prendra également soin de ré-interroger votre curseur lorsque la base de données change.

Seule mise en garde: vous devrez définir un autre observateur, car Google, dans sa sagesse infinie, a décidé de rendre son package privé. Si vous mettez la classe dans le même package que l'original (ou le compat), vous pouvez en fait utiliser l'observateur d'origine. L'observateur est un objet très léger et n'est utilisé nulle part ailleurs, donc cela ne fait pas beaucoup de différence.

Timo Ohr
la source
Mon observation dans les tests rapides est que registerContentObserver ne sera appelé contre le curseur que si le curseur est ciblé sur un fournisseur de contenu. Pouvez-vous confirmer / nier cela?
Nick Campion
1
Il ne doit pas nécessairement être un ContentProvider. Mais le curseur doit être enregistré dans un uri de notification (setNotificationUri), et il doit ensuite être notifié par quelqu'un (généralement un ContentProvider, mais peut être n'importe quoi) en appelant ContentResolver.notifyChange.
Timo Ohr
4
Ouais. sur votre CustomLoader loadInBackground() , avant de renvoyer le curseur, dites que cursor.setNotificationUri(getContext().getContentResolver(), uri);l'URI peut simplement provenir d'une chaîne aléatoire comme Uri.parse("content://query_slot1"). On dirait que ça ne se soucie pas que l'URI existe vraiment ou non. Et une fois que j'ai fait l'opération sur DB. Dire getContentResolver().notifyChange(uri, null);ferait l'affaire. Ensuite, je peux créer quelques "emplacement d'uri de requête" dans un fichier contant pour l'application avec un petit nombre de requêtes. J'ai testé l'insertion de l'enregistrement DB dans le runtime et cela semble fonctionner mais je doute toujours que ce soit une bonne pratique dessus. Toute suggestion?
Yeung
J'utilise cette méthode avec la suggestion de @Yeung et tout fonctionne, y compris le rechargement automatique du curseur lors de la mise à jour de la base de données.
DavidH
n'en a pas besoin unregisterContentObserver?
GPack
2

La troisième option proposée par Timo Ohr, associée aux commentaires de Yeung, apporte la réponse la plus simple (le rasoir d'Occam). Voici un exemple de cours complet qui fonctionne pour moi. Il existe deux règles pour utiliser cette classe.

  1. Étendez cette classe abstraite et implémentez les méthodes getCursor () et getContentUri ().
  2. Chaque fois que la base de données sous-jacente change (par exemple, après une insertion ou une suppression), assurez-vous d'appeler

    getContentResolver().notifyChange(myUri, null);

    où myUri est le même que celui renvoyé par votre implémentation de la méthode getContentUri ().

Voici le code de la classe que j'ai utilisée:

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
la source