2012-07-05 21 views
54

Tengo una rutina que ejecuta diferentes consultas contra una base de datos SQLite muchas veces por segundo. Después de un tiempo me gustaría obtener el errorSQLite Android Database Asignación de la ventana del Cursor de 2048 kb falló

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

Tenía 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 como para liberar 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.

¿Alguna idea?

EDIT: re solicitud de código de ejemplo @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 ser capaz de .setWindow() antes de dar una nueva consulta, y tienen los datos puestos en la misma CursorWindow cada vez que consigo nuevos datos .

+0

No sé cuál es el problema .. :) pero yo uso para hacer SQLiteOpenHelper Singleton clase. Entonces nunca encontré ningún problema como este. –

+0

No, no uso SQLiteOpenHelper, creo una clase de DataAccess estática que contiene una SQLiteDatabase. Esto funciona bien y dudo que el problema esté allí. El problema tiene más que ver con las bibliotecas SQLite que crean un contenedor NUEVO para colocar los resultados de cada nueva consulta, en lugar de usar el mismo contenedor una y otra vez. Y aunque puedo cerrar el cursor, la velocidad a la que el GC se limpia es más lenta que la velocidad a la que se crean los nuevos contenedores, lo que produce la acumulación de memoria. – alex

+0

¿Puedes mostrar algo de tu código, particularmente lo que intentaste hacer con tu propio 'CursorWindow'? –

Respuesta

88

En la mayoría de los casos, la causa 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(); 
} 
+0

En realidad, puede simplemente ese ejemplo para evitar la verificación nula y nula. – aij

+2

Al igual que los punteros de archivos abiertos: SIEMPRE maneje el cierre en la sección final para asegurarse de que su código exista de manera clara. – slott

11

yo sólo han experimentado este problema - y la respuesta del sugerido de no cerrar el cursor aunque válida, no era como me 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.

62

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); 
    ... 
    ... 
+6

¡Eso es genial! He hecho este estándar con 'if (BuildConfig.DEBUG) {...}' dentro de un bloque 'static {...}' de mi clase principal. –

+1

¡La mejor y la más eficiente manera de solucionar ese tipo de problema que he encontrado hasta ahora, debería ser la respuesta aceptada! – androidseb

+0

¡Genial! Pero en el lanzamiento funcionará sin el StrictMode? – alfdev

0
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(); 
    } 
    } 
} 
+0

Debe agregar algunos comentarios explicando su código – sme

+0

Límite de Breakthrough CursorWindow de solo 2 megabytes –

Cuestiones relacionadas