11

Mam niestandardową ContentProvider, która zarządza dostępem do bazy danych SQLite. Aby załadować zawartość tabeli bazy danych w ListFragment używam LoaderManager z CursorLoader i CursorAdapter:Android ContentProvider wywołuje bursts setNotificationUri() do CursorAdapter, gdy wiele wierszy jest wstawianych z operacją wsadową.

public class MyListFragment extends ListFragment implements LoaderCallbacks<Cursor> { 
    // ... 
    CursorAdapter mAdapter; 

    @Override 
    public void onActivityCreated(Bundle savedInstanceState) { 
     super.onActivityCreated(savedInstanceState); 
     mAdapter = new CursorAdapter(getActivity(), null, 0); 
     setListAdapter(mAdapter); 
     getLoaderManager().initLoader(LOADER_ID, null, this); 
    } 

    public Loader<Cursor> onCreateLoader(int id, Bundle args) { 
     return new CursorLoader(getActivity(), CONTENT_URI, PROJECTION, null, null, null); 
    } 

    public void onLoadFinished(Loader<Cursor> loader, Cursor c) { 
     mAdapter.swapCursor(c); 
    } 

    public void onLoaderReset(Loader<Cursor> loader) { 
     mAdapter.swapCursor(null); 
    } 
} 

Baza danych SQLite zostanie zaktualizowany przez zadanie w tle, który pobiera liczbę elementów z usługą internetową i wstawia te elementy do bazy danych za pomocą operacji wsadowych ContentProvider ().

Nawet jeśli jest to operacja partia, ContentProvider#insert() jest wywoływana za każdy wiersz jest wstawiany do bazy danych, a w obecnej realizacji, ContentProvider rozmowy setNotificationUri() dla każdego polecenia insert.

Powoduje to, że CursorAdapter odbiera serie powiadomień, co powoduje, że interfejs jest aktualizowany zbyt często, co powoduje denerwujący efekt migotania.

Najlepiej, gdy operacja wsadowa jest w toku, powinien istnieć sposób powiadomienia ContentObserver tylko na końcu każdej operacji wsadowej, a nie przy każdym poleceniu wstawiania.

Czy ktoś wie, czy jest to możliwe? Proszę zauważyć, że mogę zmienić implementację ContentProvider i zastąpić każdą z jej metod.

+4

Należy rozważyć wykonanie/oparcie dostawcy na czymś takim jak [SQLiteContentProvider] (http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android-apps/2.2_r1.1 /com/android/providers/calendar/SQLiteContentProvider.java/?v=source) - zapewnia wiele podstaw miłego i funkcjonalnego dostawcy opartego na SQLite - a jeśli to zrobisz - powinieneś po prostu użyć bulkInsert lub applyBatch aby zrobić twoje "inserty masy". Dodatkową korzyścią jest to, że twoje wkładki luzem zostaną wykonane w transakcji, która przyspiesza ich bardzo dużo. – Jens

+0

@ Dziękuję bardzo za wskaźnik, to wygląda dokładnie to, co byłem po. Tylko jedno pytanie: kto opracował i wypuścił ten kod? Z nagłówka wynika, że ​​jest częścią Projektu Open Source Android, ale jeśli tak jest, dlaczego nie został wypuszczony przez Google za pomocą standardowego zestawu SDK Androida? –

+2

Jest to część projektu AOSP, ale nie opublikowana w SDK - dlaczego nie wiem, ponieważ włączenie go uniemożliwiłoby wielu ludziom pisanie brzydkich implementacji "ContentProvider". – Jens

Odpowiedz

7

Aby rozwiązać ten problem, nadpisałem applyBatch i ustawiłem flagę, która blokowała inne metody wysyłania powiadomień.

volatile boolean applyingBatch=false; 
    public ContentProviderResult[] applyBatch(
     ArrayList<ContentProviderOperation> operations) 
     throws OperationApplicationException { 
    applyingBatch=true; 
    ContentProviderResult[] result; 
    try { 
     result = super.applyBatch(operations); 
    } catch (OperationApplicationException e) { 
     throw e; 
    } 
    applyingBatch=false; 
    synchronized (delayedNotifications) { 
     for (Uri uri : delayedNotifications) { 
      getContext().getContentResolver().notifyChange(uri, null); 
     } 
    } 
    return result; 
} 

I wystawiony metodę powiadomienia „Store” być wysłana, gdy partia była kompletna:

protected void sendNotification(Uri uri) { 
    if (applyingBatch) { 
     if (delayedNotifications==null) { 
      delayedNotifications=new ArrayList<Uri>(); 
     } 
     synchronized (delayedNotifications) { 
      if (!delayedNotifications.contains(uri)) { 
       delayedNotifications.add(uri); 
      } 
     } 
    } else { 
     getContext().getContentResolver().notifyChange(uri, null); 
    } 
} 

a wszelkie metody, które zatrudniają sendNotification wysyłania powiadomień, zamiast bezpośrednio wypalania powiadomienie.

Być może istnieją lepsze sposoby robienia tego - z pewnością wydaje się, że tak powinno być - ale to właśnie zrobiłem.

+1

Dziękuję za odpowiedź. +1 za pomysł, który jest jedną z opcji, które miałem na myśli, ale wolałbym raczej użyć bardziej elastycznego i ogólnego podejścia wynikającego z abstrakcyjnego ['SQLiteContentProvider'] (http://grepcode.com/file_/repository .grepcode.com/java/ext/com.google.android/android-apps/2.2_r1.1/com/android/providers/calendar/SQLiteContentProvider.java /? v = source) zaproponowana przez Jensa. –

13

Znalazłem prostsze rozwiązanie, które odkryłem w aplikacji Google I/O. Musisz tylko przesłonić metodę applyBatch w swoim dostawcy treści i wykonać wszystkie operacje w transakcji. Powiadomienia nie są wysyłane do momentu transakcji zobowiązuje się, co powoduje zminimalizowanie liczby przełączeń powiadomień ContentProvider które są wysyłane:

@Override 
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 
     throws OperationApplicationException { 

    final SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); 
    db.beginTransaction(); 
    try { 
     final int numOperations = operations.size(); 
     final ContentProviderResult[] results = new ContentProviderResult[numOperations]; 
     for (int i = 0; i < numOperations; i++) { 
      results[i] = operations.get(i).apply(this, results, i); 
     } 
     db.setTransactionSuccessful(); 
     return results; 
    } finally { 
     db.endTransaction(); 
    } 
} 
+0

Dzięki @Dia, wydaje się najlepszym i najprostszym rozwiązaniem. A ponieważ jest to zbyt proste i ogólne, zastanawiam się, dlaczego google nie czyni tego jako domyślnym dla wszystkich operacji wsadowych. – xandy

+0

Dla mnie to odpala tyle samo zdarzeń, ponieważ wciąż wywołuje metodę wstawiania (...) wiele razy i stąd wywołanie jego notifyChange (uri, null), która znajduje się we wstawianej (...) metodzie. – startoftext

+0

Tylko jeden komentarz, "co powoduje zminimalizowanie liczby" w rzeczywistości po prostu przekazuje powiadomienie do końca realizacji. Dzięki za proponowane rozwiązanie! – Juancho

0

w komentarzu do pierwotnej odpowiedzi, Jens skierował nas ku SQLiteContentProvider w AOSP. Jednym z powodów, dla którego nie jest to (jeszcze?) W SDK, może być to, że AOSP wydaje się zawierać wiele odmian tego kodu.

Na przykład com.android.browser.provider.SQLiteContentProvider wydaje się być nieco bardziej kompletne rozwiązanie, zawierające zasadę „Opóźniony powiadomienia” proponowane przez Phillip Fitzsimmons zachowując dostawcę bezpieczny wątku przy użyciu ThreadLocal dla partii flagą i synchronizowania dostępu do opóźnionego zgłoszenia zestaw.

Mimo że dostęp do zestawu identyfikatorów URI, które mają być powiadamiane o zmianie, jest zsynchronizowany, wciąż mogę sobie wyobrazić, że mogą wystąpić warunki wyścigu.Na przykład, jeśli długa operacja powoduje umieszczenie niektórych powiadomień, zostaje wyprzedzona przez mniejszą operację wsadową, która uruchamia powiadomienia i usuwa zestaw przed zatwierdzeniem pierwszej operacji.

Mimo to powyższa wersja wydaje się najlepszym wyborem jako punkt odniesienia przy wdrażaniu własnego operatora.

Powiązane problemy