Android SQLite и огромные наборы данных
Мы создаем приложение для клиента с сотнями мегабайт HTML в базах данных SQLite. Мы внедрили способ запроса этих данных и прокручиваем все это достаточно быстро. Проблема в том, что некоторые из баз данных имеют очень большие запросы (20 000+ строк), и мы видим ошибки, когда мы увеличиваем запросы по мере прокрутки пользователя. Поэтому, я думаю, вопрос в том, какие параметры у нас есть при запросе и отображении десятков тысяч строк данных в Android?
Вот стек, который мы видим:
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
Вот наш код 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());
}
}
Ответы
Ответ 1
какие параметры у нас есть при запросе и отображение десятков тысяч строки данных в Android?
Вы имеете в виду, кроме того, что вы говорите, что чтение 20 000+ строк на 3,5-дюймовом ЖК-экране - это бит-гуано, сумасшедший? -)
Похоже, что CursorWindow
, который используется где-то под обложками, имеет проблемы с управлением > 17 000 строк. Это может быть одной из двух вещей:
- Вы находитесь вне кучи. С 16-мегабайтной некомпактной кучей и тем, что a
Cursor
содержит весь набор результатов в куче, это не может быть и речи.
-
CursorWindow
поддерживает только 1 Мбайт данных, что является сообщением об ошибке более прямым.
Если существует логический способ разделить ваши запросы на дискретные куски, вы можете делать инкрементные запросы и использовать CursorJoiner
, чтобы сшить их вместе и посмотреть, помогает ли это.
Но, со всей серьезностью, 20 000+ строк на 3,5-дюймовом экране, на устройстве, которое наиболее близко напоминает 12-летний компьютер в лошадиных силах, действительно много спрашивают.
Ответ 2
Если вам действительно нужно, вы также можете разделить свои данные и прочитать фрагменты следующим образом:
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();
Он работает под 2 с для строк 5k, если вы указали таблицу.
Спасибо,
Arkde
Ответ 3
По моему опыту, ограничивающие запросы заставляют намного больше времени получать результаты, потому что запуск новых курсоров дорог для низких лимитов. Я попытался сделать 62k строк с 1k и даже 10k ограничением только сейчас, и он очень медленный и непригодный, так как я должен запустить более 6 курсоров. Я просто придерживаюсь не поддержки 2.3.3.... Это последняя сборка, в которой я получаю CursorWindow ERROR вместо WARN.
Вот что я сделал. Это, наверное, лучший алгоритм, который я думаю. Не нужно делать запросы дважды и т.д. Однако он может быть довольно медленным с большими запросами и небольшими ограничениями, поэтому вам нужно проверить, что работает лучше всего. В моем случае он не достаточно быстрый для моих целей, так как он не обрабатывает строки 62k.
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
}