La asignación de la ventana del cursor de la base de datos SQLite Android de 2048 kb falló

Tengo una rutina que ejecuta diferentes consultas contra una base de datos SQLite muchas veces por segundo. Después de un tiempo, obtendría el error

"android.database.CursorWindowAllocationException: - Cursor window allocation of 2048 kb failed. # Open Cursors = " aparece en LogCat.

Tuve el uso de la memoria de registro de la aplicación, y de hecho, cuando el uso alcanza un cierto límite, recibo este error, lo que implica que se agote. Mi intuición me dice que el motor de base de datos está creando un NUEVO buffer (CursorWindow) cada vez que ejecuto una consulta, y aunque marque el .close () los cursores, ni el recolector de basura ni SQLiteDatabase.releaseMemory() son lo suficientemente rápidos en liberando memoria. Creo que la solución puede consistir en “forzar” a la base de datos a escribir siempre en el mismo búfer y no crear nuevos, pero no he podido encontrar la manera de hacerlo. He intentado crear una instancia de mi propio CursorWindow, y he intentado configurarlo y SQLiteCursor en vano.

¿Algunas ideas?

EDIT: solicitud de código de ejemplo de @GrahamBorland:

 public static CursorWindow cursorWindow = new CursorWindow("cursorWindow"); public static SQLiteCursor sqlCursor; public static void getItemsVisibleArea(GeoPoint mapCenter, int latSpan, int lonSpan) { query = "SELECT * FROM Items"; //would be more complex in real code sqlCursor = (SQLiteCursor)db.rawQuery(query, null); sqlCursor.setWindow(cursorWindow); } 

Idealmente, me gustaría poder .setWindow() antes de dar una nueva consulta, y tener los datos puestos en el mismo CursorWindow cada vez que obtengo nuevos datos.

La causa más frecuente de este error son los cursores no cerrados. Asegúrese de cerrar todos los cursores después de usarlos (incluso en el caso de un error).

 Cursor cursor = null; try { cursor = db.query(... // do some work with the cursor here. } finally { // this gets called even if there is an exception somewhere above if(cursor != null) cursor.close(); } 

Si tiene que buscar una cantidad significativa de código SQL, puede acelerar su depuración colocando el siguiente fragmento de código en su MainActivity para habilitar StrictMode. Si se detectan objetos de la base de datos filtrados, su aplicación se bloqueará y la información de registro mostrará exactamente dónde se encuentra su fuga. Esto me ayudó a localizar un cursor deshonesto en cuestión de minutos.

 @Override protected void onCreate(Bundle savedInstanceState) { if (BuildConfig.DEBUG) { StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(savedInstanceState); ... ... 

Acabo de experimentar este problema, y ​​la respuesta sugerida de no cerrar el cursor mientras era válida, no fue cómo lo arreglé. Mi problema era cerrar la base de datos cuando SQLite intentaba repoblar su cursor. Abriría la base de datos, consultaría la base de datos para obtener un cursor sobre un conjunto de datos, cerraría la base de datos e iteraría sobre el cursor. Me di cuenta de que cada vez que tocaba un determinado registro en ese cursor, mi aplicación se bloqueaba con el mismo error en OP.

Supongo que para que el cursor acceda a ciertos registros, necesita volver a consultar la base de datos y, si está cerrada, arrojará este error. Lo solucioné al no cerrar la base de datos hasta que había completado todo el trabajo que necesitaba.

De hecho, hay un tamaño máximo de Android SQLite que las ventanas del cursor pueden tomar y que es de 2MB, cualquier cantidad mayor a este tamaño daría como resultado el error anterior. En su mayoría, este error es causado por una gran matriz de bytes de imágenes almacenada como blob en la base de datos sql o cadenas demasiado largas. Así es como lo arreglé.

Crea una clase java ej. FixCursorWindow y poner debajo el código en él.

  public static void fix() { try { Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); field.setAccessible(true); field.set(null, 102400 * 1024); //the 102400 is the new size added } catch (Exception e) { e.printStackTrace(); } } 

Ahora vaya a su clase de aplicación (cree una si no la tiene) y haga una llamada a FixCursorWindow de esta manera

la aplicación de clase pública extiende la aplicación {

 public void onCreate() { super.onCreate(); CursorWindowFixer.fix(); } 

}

Finalmente, asegúrese de incluir su clase de aplicación en su manifiesto en la etiqueta de la aplicación como esta

  android:name=".App"> 

Eso es todo, debería funcionar perfectamente ahora.

 public class CursorWindowFixer { public static void fix() { try { Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); field.setAccessible(true); field.set(null, 102400 * 1024); } catch (Exception e) { e.printStackTrace(); } } }