2009-09-10 14 views
18

Tworzymy aplikację dla klienta, który ma setki megabajtów HTML w bazach danych SQLite. Wdrożyliśmy sposób sprawdzania tych danych i przewijać je w dość szybki sposób. Problem polega na tym, że niektóre z baz danych mają bardzo duże zapytania (ponad 20 000 wierszy) i widzimy błędy, gdy rośnie liczba zapytań, gdy użytkownik przewija. Sądzę więc, że pytanie brzmi: jakie opcje mamy w przypadku wysyłania zapytań i wyświetlania dziesiątek tysięcy wierszy danych w systemie Android?Android SQLite i ogromne zestawy danych

Oto StackTrace widzimy:

09-10 19:19:12.575: WARN/IInputConnectionWrapper(640): showStatusIcon on inactive InputConnection 
09-10 19:19:18.226: DEBUG/dalvikvm(640): GC freed 446 objects/16784 bytes in 330ms 
09-10 19:19:32.886: ERROR/CursorWindow(19416): need to grow: mSize = 1048576, size = 36, freeSpace() = 30, numRows = 17717 
09-10 19:19:32.896: ERROR/CursorWindow(19416): not growing since there are already 17717 row(s), max size 1048576 
09-10 19:19:32.916: ERROR/CursorWindow(19416): The row failed, so back out the new row accounting from allocRowSlot 17716 
09-10 19:19:33.005: ERROR/Cursor(19416): Failed allocating fieldDir at startPos 0 row 17716 
09-10 19:19:35.596: DEBUG/Cursor(19416): finish_program_and_get_row_count row 24315 
09-10 19:19:41.545: DEBUG/dalvikvm(698): GC freed 2288 objects/126080 bytes in 260ms 
09-10 19:19:43.705: WARN/KeyCharacterMap(19416): No keyboard for id 0 
09-10 19:19:43.717: WARN/KeyCharacterMap(19416): Using default keymap: /system/usr/keychars/qwerty.kcm.bin 
09-10 19:20:04.705: ERROR/CursorWindow(19416): need to grow: mSize = 1048576, size = 17, freeSpace() = 3, numRows = 17094 
09-10 19:20:04.716: ERROR/CursorWindow(19416): not growing since there are already 17094 row(s), max size 1048576 
09-10 19:20:04.726: ERROR/Cursor(19416): Failed allocating 17 bytes for text/blob at 17093,2 
09-10 19:20:05.656: DEBUG/Cursor(19416): finish_program_and_get_row_count row 5257 
09-10 19:24:54.685: DEBUG/dalvikvm(637): GC freed 9297 objects/524176 bytes in 247ms 
09-10 19:32:07.656: DEBUG/dalvikvm(19416): GC freed 9035 objects/495840 bytes in 199ms 

Oto nasz kod CursorAdapter:

private class MyAdapter extends ResourceCursorAdapter { 

    public MyAdapter(Context context, Cursor cursor) { 
     super(context, R.layout.my_row, cursor);   
    } 

    public void bindView(View view, Context context, Cursor cursor) {     
     RowData data = new RowData(); 
     data.setName(cursor.getInt(cursor.getColumnIndex("name"))); 

     TextView tvItemText = (TextView)view.findViewById(R.id.tvItemText); 
     tvItemText.setText(data.getName()); 

     view.setTag(data); 
    } 

    @Override 
    public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 
     /* Display the progress indicator */ 
     updateHandler.post(onFilterStart); 

     /* Run the actual query */    
     if (constraint == null) { 
      return myDbObject.getData(null);      
     } 

     return myDbObject.getData(constraint.toString());     
    }    
} 
+0

"jakie opcje mamy dla wysyłania zapytań i wyświetlania dziesiątek tysięcy wierszy danych w systemie Android?" -> eh? Nie mogę nawet pomyśleć, dlaczego chcesz to zrobić. – SK9

+0

@ SK9 To było do przenoszenia aplikacji medycznej odniesienia z iPhone'a na Androida. Dosłownie zawierały dziesiątki tysięcy rekordów w tych bazach danych SQLite. Każda rozsądna osoba zacznie wprowadzać wyszukiwane hasło, aby zawęzić listę, ale aplikacja iPhone obsługuje ręczne przewijanie wszystkich danych. Tak więc oczekiwali, że aplikacja na Androida zachowa się w ten sam sposób. – MattC

+0

Mam podobny wymóg. Zdziwiłoby się, jak wiele organizacji korzysta z wyspecjalizowanych aplikacji w ten sposób. – gonzobrains

Odpowiedz

26

jakie opcje mamy w odpytywanie i wyświetlania dziesiątki tysięcy wiersze danych w systemie Android?

To znaczy, poza informacją, że czytanie 20,000+ wiersze na 3,5" ekranie LCD jest bat guano szalony? ;-)

Wygląda CursorWindow, który jest używany gdzieś pod kołdrą, to problemy Zarządzający> 17000 wierszy, które może być jedna z dwóch rzeczy.

  1. Jesteś z przestrzeni sterty z 16MB bez ubijania sterty, oraz fakt, że Cursor posiada cały zestaw wyników w stercie, że. nie wchodzi w grę.
  2. CursorWindow obsługuje tylko 1 MB danych, co jest tym, co komunikat o błędzie sugeruje bardziej bezpośrednio.

Jeśli istnieje logiczny sposób podzielić zapytania na oddzielne kawałki, można zrobić przyrostowych zapytań i wykorzystywać CursorJoiner zszyć je razem, i sprawdzić, czy to pomaga.

Ale z całą powagą, 20,000+ wiersze w ekran 3.5" , na urządzeniu, które najbardziej przypomina 12-letni komputer w KM, jest naprawdę pytając dużo.

+0

Niestety, działa jak urok na iPhonie, a klient oczekuje podobnej wydajności. W tym przypadku jest to po prostu łamanie i to wszystko jest złe. Usuwamy zapytanie, aby załadować kilka wierszy naraz. Problem polega na tym, że "rzucają się" na sam dół i spodziewają się, że znajdą się na samym dole listy. – MattC

+3

* wzruszenie ramion * Znajdź sposób, aby wyniki zapytania były mniejsze niż 1 MB. Zażądaj mniej kolumn. Spakuj dane lepiej (np. Nie używaj 8 kolumn INTEGER dla wartości boolowskiej - użyj jednej kolumny INTEGER i bitmask). Wymyśl UX, który pozwala użytkownikowi konsekwentnie filtrować lepiej, dzięki czemu nie pojawi się zestaw wyników rzędu 20K. Możesz spróbować pracować z SQLiteCursor/SQLiteQuery za pomocą CursorWindow, ale podejrzewam, że twój występ będzie nieprzyjemny. – CommonsWare

+0

Chyba zastanawiam się, jaki jest najlepszy sposób na płynne przewijanie listy, gdy masz stolik z 42 000 przedmiotów i ładujesz 30-40 na raz. Działa dla mniejszych tabel, ale użycie LIMIT i OFFSET w zapytaniu dla tej konkretnej bazy danych nadal powoduje przewijanie ANR i czkawkę. – MattC

5

Jeśli naprawdę potrzebujesz że można również podzielić dane i czytać fragmenty tak:.

int limit = 0; 
    while (limit + 100 < numberOfRows) { 
     //Compose the statement 
     String statement = "SELECT * FROM Table ORDER someField LIMIT '"+ limit+"', 100"; 
     //Execute the query 
     Cursor cursor = myDataBase.rawQuery(statement, null); 
     while (cursor.moveToNext()) { 
      Product product = new Product(); 
      product.setAllValuesFromCursor(cursor); 
      productsArrayList.add(product); 
     } 
     cursor.close(); 
     limit += 100; 
} 

//Compose the statement 
String statement = "SELECT * FROM Table ORDER someField LIMIT '"+ (numberOfRows - limit)+"', 100"; 
//Execute the query 
Cursor cursor = myDataBase.rawQuery(statement, null); 

while (cursor.moveToNext()) { 
    Product product = new Product(); 
    product.setAllValuesFromCursor(cursor); 
    productsArrayList.add(product); 
} 
cursor.close(); 

to działa na podstawie 2 s do 5k wierszy jeśli indeksowane tabeli

Dzięki, Arkde

0

Z mojego doświadczenia wynika, że ​​ograniczanie zapytań powoduje, że uzyskanie wyników jest dużo dłuższe, ponieważ uruchamianie nowych kursorów jest drogie w przypadku niskich limitów. Próbowałem zrobić 62k wierszy z limitem 1k, a nawet 10k teraz i jest bardzo wolny i bezużyteczny, ponieważ muszę uruchomić więcej niż 6 kursorów. Po prostu pozostanę z nieobsługiwaniem 2.3.3 .... Jest to najnowszy build, który dostaję CursorWindow ERROR zamiast WARN.

Oto co zrobiłem. Prawdopodobnie jest to najlepszy algorytm. Nie musisz robić zapytań dwa razy itd. Jednak może się to zdarzyć dość szybko z dużymi pytaniami i małymi limitami, więc musisz przetestować, co działa najlepiej. W moim przypadku nie jest wystarczająco szybki dla moich celów, ponieważ nie radzi sobie dobrze z 62 tys. Wierszy.

int cursorCount = 0; 
int limit = 1000; //whatever you want 
while (true) 
{ 
    Cursor cursor = builder.query(mDatabaseHelper.getReadableDatabase(), columns, selection, selectionArgs, null, null, null, Integer.toString(limit)); 
    if (cursor == null) { 
     return null; 
    } else if (!cursor.moveToFirst()) { //if it is empty, return null 
     return null; 
    } 
    cursorCount = cursor.getCount(); 
    if (cursorCount % limit != 0) 
    { 
     return cursor; 
    } 
    limit+=1000; //same amount as the one above 
}