Użycie CursorLoader bez ContentProvider


107

Dokumentacja Android SDK mówi, że startManagingCursor()metoda jest pozbawiona wartości:

Ta metoda jest przestarzała. Zamiast tego użyj nowej klasy CursorLoader z LoaderManager; jest to również dostępne na starszych platformach za pośrednictwem pakietu zgodności z systemem Android. Ta metoda pozwala aktywności zająć się zarządzaniem cyklem życia danego kursora w oparciu o cykl życia działania. Oznacza to, że gdy aktywność zostanie zatrzymana, automatycznie wywoła dezaktywację () na danym kursorze, a po późniejszym ponownym uruchomieniu wywoła dla Ciebie requery (). Gdy aktywność zostanie zniszczona, wszystkie zarządzane kursory zostaną automatycznie zamknięte. Jeśli celujesz w HONEYCOMB lub nowszy, zamiast tego rozważ użycie LoaderManager, dostępnego przez getLoaderManager ()

Więc chciałbym użyć CursorLoader. Ale jak mogę go używać z niestandardowym CursorAdapteri bez ContentProvider, kiedy potrzebuję URI w konstruktorze CursorLoader?


@Alex Lockwood, dlaczego używamy CursorAdapter bez ContentProvider, zasugeruj mi stackoverflow.com/questions/20419278/ ...

dlaczego używamy CursorAdapter bez ContentProvider, zasugeruj mi stackoverflow.com/questions/20419278/ ...

Odpowiedzi:


155

Napisałem prosty CursorLoader , który nie potrzebuje dostawcy treści:

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

Potrzebuje tylko AsyncTaskLoader klasy. Albo ten w systemie Android 3.0 lub nowszym, albo ten, który jest dostarczany z pakietem zgodności.

Ja też napisałemListLoader , która jest zgodna z LoadManageri jest używany do pobierania rodzajowe java.util.Listkolekcji.


13
Znalazłem fajny przykład kodu, który to wykorzystuje - bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo/ ... - uznał to za bardzo przydatne!
— Shushu

@Cristian Thanks za przykład. Jaka jest licencja związana z twoją klasą. Jak można go ponownie wykorzystać?
— codinguser

2
Licencja to Apache 2.0; możesz go ponownie użyć, gdzie / kiedy chcesz. Daj mi znać, jeśli masz jakieś ulepszenia.
— Cristian

14
Świetne rzeczy! Użytkownicy powinni być świadomi jednego ograniczenia, którym jest brak mechanizmu odświeżania po zmianach danych (tak jak powinny to robić
— programy ładujące

1
@Jadeye tutaj masz człowiek: ListLoader i SupportListLoader
— Cristian

23

Napisz własny moduł ładujący, który używa Twojej klasy bazy danych zamiast dostawcy treści. Najprostszym sposobem jest po prostu pobranie źródła CursorLoaderklasy z biblioteki zgodności i zastąpienie zapytań dostawców zapytaniami do własnej klasy pomocniczej db.


1
To moim zdaniem najłatwiejszy sposób. W mojej aplikacji utworzyłem CursorLoaderdescendat do zarządzania kursorem SQLite, poza konstruktorem potrzebowałem tylko przesłonić loadInBackgroundmetodę, aby zastąpić zapytanie dostawcy moim zapytaniem kursora
— Jose_GD

14

SimpleCursorLoader jest prostym rozwiązaniem, jednak nie obsługuje aktualizowania modułu ładującego w przypadku zmiany danych. CommonsWare ma bibliotekę loaderex, która dodaje SQLiteCursorLoader i obsługuje ponowne zapytania dotyczące zmian danych.

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


2
Jednak aby skorzystać z automatycznego ponownego wysyłania zapytań, musisz użyć tego samego modułu ładującego dla interfejsu użytkownika, a także dla aktualizacji, co ogranicza jego użyteczność dla usług działających w tle.
— ge0rg

12

Trzecią opcją byłoby po prostu zastąpienie 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;
    }
};

Spowoduje to również ponowne wysłanie zapytania do kursora po zmianie bazy danych.

Jedyne zastrzeżenie: musisz zdefiniować innego obserwatora, ponieważ Google w swojej nieskończonej mądrości zdecydował, że ich pakiet będzie prywatny. Jeśli umieścisz klasę w tym samym pakiecie, co oryginalna (lub zgodna), możesz faktycznie użyć oryginalnego obserwatora. Obserwator jest bardzo lekkim obiektem i nie jest używany nigdzie indziej, więc nie robi to dużej różnicy.


Z moich obserwacji w szybkich testach wynika, że ​​registerContentObserver będzie wywoływany w stosunku do kursora tylko wtedy, gdy kursor jest skierowany do dostawcy treści. Czy możesz to potwierdzić / zaprzeczyć?
— Nick Campion,

1
Nie musi to być dostawca treści. Ale kursor musi być zarejestrowany w uri powiadomienia (setNotificationUri), a następnie musi zostać powiadomiony przez kogoś (zwykle ContentProvider, ale może to być cokolwiek) przez wywołanie ContentResolver.notifyChange.
— Timo Ohr,

4
Tak. na twoim CustomLoader loadInBackground() , przed zwróceniem kursora, powiedz, że cursor.setNotificationUri(getContext().getContentResolver(), uri);uri może pochodzić z losowego ciągu, takiego jak Uri.parse("content://query_slot1"). Wygląda na to, że nie obchodzi go, czy uri naprawdę istnieje, czy nie. A kiedy zrobiłem operację na DB. Say getContentResolver().notifyChange(uri, null);załatwi sprawę. Następnie mogę utworzyć kilka "slotów zapytań uri" w stałym pliku dla aplikacji z niewielką liczbą zapytań. Testuję wstawianie rekordu DB w czasie wykonywania i wydaje się, że działa, ale nadal wątpię, czy jest to dobra praktyka onit. Jakieś sugestie?
— Yeung,

Używam tej metody z sugestią @Yeung i wszystko działa, łącznie z automatycznym przeładowywaniem kursora przy aktualizacji bazy danych.
— DavidH

czy nie potrzebuje wyrejestrowania się z treścią?
— GPack

2

Trzecia opcja zaproponowana przez Timo Ochra, wraz z komentarzami Yeunga, dają najprostszą odpowiedź (brzytwa Ockhama). Poniżej znajduje się przykład kompletnej klasy, która działa dla mnie. Istnieją dwie zasady korzystania z tej klasy.

  1. Rozszerz tę klasę abstrakcyjną i zaimplementuj metody getCursor () i getContentUri ().
  2. Za każdym razem, gdy podstawowa baza danych ulegnie zmianie (np. Po wstawieniu lub usunięciu), należy wywołać

    getContentResolver().notifyChange(myUri, null);

    gdzie myUri to ta sama wartość, która została zwrócona przez implementację metody getContentUri ().

Oto kod klasy, której użyłem:

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();
  }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.