Несмотря на наличие ограничений для курсора, могут обрабатываться миллионы строк.
Ограничение заключается в том, что строка содержит больше данных, чем может храниться в CursorWindow (1M (более ранние версии) или 2M).Обычно это происходит только с очень большими объектами, такими как изображения или видео.
Пример с 3 000 000 строк
Вот пример приложения, которое вставляет и извлекает 3 миллиона строк, которые обрабатываются (очень много времени).
1.DBDone.java
Интерфейс для установления завершения потоков, не относящихся к пользовательскому интерфейсу
- (в противном случае не запуск из основного потока пользовательского интерфейса может привести к сбою из-за отсутствия приложения (ANR))
- 1000 строк вряд ли приведет к ANR
: -
public interface DBDone {
void dbDone();
}
2.DBHelper.java
Помощник по базам данных с некоторыми основными методами, позволяющими добавлять и извлекать данные (также не позволяет выбирать режим WAL или Journal, последний используется так, что до Android 9 это режим по умолчанию).
public class DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "mydb";
public static final int DBVERSION = 1;
public static final String TBL_TABLENAME = "table_name";
public static final String COL_SOMECOLUMN1 = "some_column_1";
public static final String COL_SOMECOLUMN2 = "some_column_2";
public static final String crt_tablename_sql = "CREATE TABLE IF NOT EXISTS " + TBL_TABLENAME + "(" +
COL_SOMECOLUMN1 + " TEXT, " +
COL_SOMECOLUMN2 + " TEXT" +
")";
private static boolean mWALMode = false;
SQLiteDatabase mDB;
public DBHelper(Context context, boolean wal_mode) {
super(context, DBNAME, null, DBVERSION);
mWALMode = wal_mode;
mDB = this.getWritableDatabase();
}
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
if (mWALMode) {
db.enableWriteAheadLogging();
} else {
db.disableWriteAheadLogging();
}
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(crt_tablename_sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
}
public long insert(String c1_value, String c2_value) {
String nullcolumnhack = null;
ContentValues cv = new ContentValues();
if ((c1_value == null && c2_value == null)) {
nullcolumnhack = COL_SOMECOLUMN1;
}
if (c1_value != null) {
cv.put(COL_SOMECOLUMN1,c1_value);
}
if (c2_value != null) {
cv.put(COL_SOMECOLUMN2,c2_value);
}
return mDB.insert(TBL_TABLENAME,nullcolumnhack,cv);
}
public long insertJustColumn1(String c1_value) {
return this.insert(c1_value, null);
}
public long insertJustColumn2(String c2_value) {
return this.insert(null,c2_value);
}
public Cursor getAllFromTableName() {
return this.getAll(TBL_TABLENAME);
}
public Cursor getAll(String table) {
return mDB.query(table,null,null,null,null,null,null);
}
}
3.MainActivity.java
MainActivity создает экземпляр DatabaseHelper (mDBHlpr, отмечая, что это создаст базу данных и базовые таблицы, поскольку конструктор форсирует создание, получая вызов getWritableDatabase ).
Затем вызывается метод dbDone, который, если для mStage установлено значение 0, очистит таблицу в новом потоке.
Когда таблица очищается, вызывается dbDone. MStage будет равен 1, поэтому данные будут добавлены (если их нет, чего не должно быть, поскольку таблица была очищена).
- Примечание: добавление 3 000 000 строк может занять некоторое время.
Когда данные вставлены, вызывается dbDone, а mStage теперь равен 2, а некоторая информация будет записана в журнал.после извлечения курсора появятся все строки.Все строки в Курсоре пройдены, и подсчитано количество строк, у которых оба столбца равны нулю.
- Количество извлеченных строк (3 000 000) будет записано в журнал.
- Будет подсчитано количество строк, у которых оба столбца равны нулю (просто чтобы сделать что-то скурсор).Число будет меняться при случайном добавлении нулей.
: -
public class MainActivity extends AppCompatActivity implements DBDone {
DBHelper mDBHlpr;
int mStage = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDBHlpr = new DBHelper(this,false);
dbDone();
}
// Add some data but not in the UI Thread
private void addData() {
new Thread(new Runnable() {
@Override
public void run() {
addSomeData(3000000);
dbDone(); // All done so notify Main Thread
}
}
).start();
}
public void dbDone() {
switch (mStage) {
case 0:
emptyTable();
break;
case 1:
addData();
break;
case 2:
logSomeInfo();
break;
}
mStage++;
}
/**
* Add some rows (if none exist) with random data
* @param rows_to_add number of rows to add
*/
private void addSomeData(int rows_to_add) {
Log.d("ADDSOMEDATA","The addSomeData method hass been invoked (will run in a non UI thread)");
SQLiteDatabase db = mDBHlpr.getWritableDatabase();
if(DatabaseUtils.queryNumEntries(db,DBHelper.TBL_TABLENAME) > 0) return;
// Random data that can be added to the first column
String[] potential_data1 = new String[]{null,"complete","started","stage1","stage2","stage3","stage4","stage5"};
// Random data that can be added to the second column
String[] potential_data2 = new String[]{null,"something else","another","different","unusual","normal"};
Random r = new Random();
db.beginTransaction();
for (int i=0; i < rows_to_add; i++) {
mDBHlpr.insert(
potential_data1[(r.nextInt(potential_data1.length))],
potential_data2[(r.nextInt(potential_data2.length))]
);
}
db.setTransactionSuccessful();
db.endTransaction();
}
/**
* Log some basic info from the Cursor always traversinf the entire cursor
*/
private void logSomeInfo() {
Log.d("LOGSOMEINFO","The logSomeInfo method has been invoked.");
Cursor csr = mDBHlpr.getAllFromTableName();
StringBuilder sb = new StringBuilder("Rows in Cursor = " + String.valueOf(csr.getCount()));
int both_null_column_count = 0;
while (csr.moveToNext()) {
if (csr.getString(csr.getColumnIndex(DBHelper.COL_SOMECOLUMN1)) == null && csr.getString(csr.getColumnIndex(DBHelper.COL_SOMECOLUMN2)) == null) {
both_null_column_count++;
}
}
sb.append("\n\t Number of rows where both columns are null is ").append(String.valueOf(both_null_column_count));
Log.d("LOGSOMEINFO",sb.toString());
}
/**
* Empty the table
*/
private void emptyTable() {
Log.d("EMPTYTABLE","The emptyTable method has been invoked (will run in a non UI thread)");
new Thread(new Runnable() {
@Override
public void run() {
mDBHlpr.getWritableDatabase().delete(DBHelper.TBL_TABLENAME,null,null);
dbDone();
}
}).start();
}
}
Журнал может также содержать полные сообщения CursorWindow, НО они обрабатываются (какнарушающая строка будет включена в следующее CursorWindow), например: -
2018-12-30 10:19:10.862 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 404 bytes, free space 204 bytes, window size 2097152 bytes
2018-12-30 10:19:11.856 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 24 bytes, free space 11 bytes, window size 2097152 bytes
2018-12-30 10:19:12.377 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 7 bytes, free space 4 bytes, window size 2097152 bytes
2018-12-30 10:19:12.902 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 8 bytes, free space 1 bytes, window size 2097152 bytes
2018-12-30 10:19:13.433 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 24 bytes, free space 1 bytes, window size 2097152 bytes
2018-12-30 10:19:13.971 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 24 bytes, free space 21 bytes, window size 2097152 bytes
2018-12-30 10:19:14.505 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 24 bytes, free space 18 bytes, window size 2097152 bytes
2018-12-30 10:19:15.045 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 404 bytes, free space 187 bytes, window size 2097152 bytes
2018-12-30 10:19:15.598 2799-2817/so53958115.so53958115 W/CursorWindow: Window is full: requested allocation 7 bytes, free space 0 bytes, window size 2097152 bytes
...........
Время: -
Журнал включает в себя: -
2018-12-30 10:17:04.610 2799-2799/? D/EMPTYTABLE: The emptyTable method has been invoked (will run in a non UI thread)
2018-12-30 10:17:04.615 2799-2817/? D/ADDSOMEDATA: The addSomeData method hass been invoked (will run in a non UI thread)
2018-12-30 10:19:10.506 2799-2817/so53958115.so53958115 D/LOGSOMEINFO: The logSomeInfo method has been invoked.
2018-12-30 10:20:17.803 2799-2817/so53958115.so53958115 D/LOGSOMEINFO: Rows in Cursor = 3000000
Number of rows where both columns are null is 62604
Итак, потребовалось:- - 5 мс, чтобы очистить (уже пустую) таблицу.- 2 минуты и 6 секунд, чтобы добавить 3 000 000 строк.- 1 минута и 7,5 секунды для извлечения и перемещения курсора (не то, что вы обычно извлекаете столько строк)
Но самое главное, что курсор справился с 3 000 000 строк.Вы также можете видеть, что CursorWindow в этом случае составляет 2M (2097152 байта).
Заключение
Маловероятно, что 1000 строк слишком велики для курсора, хотя это может бытьчто некоторые строки при хранении изображений / видео / длинных текстов максимально превышают размер, который может обрабатывать курсор.
Эта проблема, скорее всего, связана с другими причинами, которые могут быть установлены только через стек.след в журнале.
Таким образом, невозможно предоставить конкретный ответ без отслеживания стека или более полной информации.