2012-07-05 5 views
54

Ho una routine che esegue query diverse su un database SQLite molte volte al secondo. Dopo un po 'che avrei avuto l'erroreSQLite Database Android Allocazione della finestra del cursore di 2048 kb non riuscita

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

ho avuto l'utilizzo della memoria di registro applicazione, e in effetti quando l'utilizzo raggiunge un certo limite l'ottengo questo errore, il che implica che si esaurisca. La mia intuizione mi dice che il motore di database sta creando un NUOVO buffer (CursorWindow) ogni volta che eseguo una query, e anche se contrassegno i cursori .close(), né il garbage collector né SQLiteDatabase.releaseMemory() sono abbastanza veloci per liberare memoria. Penso che la soluzione potrebbe risiedere nel "forzare" il database a scrivere sempre nello stesso buffer e non a crearne di nuovi, ma non sono riuscito a trovare un modo per farlo. Ho provato a creare un'istanza del mio CursorWindow, e ho provato a impostarlo su SQLiteCursor senza risultato.

¿Qualche idea?

EDIT: re richiesta di codice di esempio da @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 mi piacerebbe essere in grado di .setWindow() prima di dare una nuova query, ed avere i dati inseriti nella stessa CursorWindow ogni volta ricevo nuovi dati .

+0

Non so qual è il problema .. :) ma che utilizzo per fare SQLiteOpenHelper classe Singleton. Quindi non ho mai riscontrato problemi come questo. –

+0

No, non utilizzo SQLiteOpenHelper, creo una classe DataAccess statica che contiene un SQLiteDatabase. Funziona bene e dubito che il problema ci sia. Il problema ha più a che fare con le librerie SQLite che creano un NUOVO contenitore per posizionare i risultati di ogni nuova query, piuttosto che usare lo stesso contenitore più e più volte. E anche se riesco a chiudere il cursore, la velocità di pulizia del GC è più lenta della velocità con cui i nuovi contenitori vengono creati, producendo così il maiale della memoria. – alex

+0

Puoi mostrare un po 'del tuo codice, in particolare quello che hai provato a fare con l'impostazione del proprio 'CursorWindow'? –

risposta

88

Molto spesso la causa di questo errore sono cursori non chiusi. Assicurati di chiudere tutti i cursori dopo averli utilizzati (anche in caso di errore).

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

In realtà è sufficiente quell'esempio per evitare il controllo nullo e nullo. – aij

+2

Proprio come i puntatori di file aperti, gestisci SEMPRE la chiusura nella sezione finale per assicurarti che il tuo codice esista in modo pulito. – slott

11

ho appena vissuto questo problema - e la risposta del suggerito di non chiudere il cursore, mentre validi, non era come ho riparato. Il mio problema era chiudere il database quando SQLite stava provando a ripopolare il suo cursore. Vorrei aprire il database, interrogare il database per ottenere un cursore su un set di dati, chiudere il database e scorrere il cursore. Ho notato che ogni volta che colpivo un determinato record in quel cursore, la mia app si bloccava con lo stesso errore in OP.

Presumo che per il cursore per accedere alcuni record, ha bisogno di ri-query al database e se è chiuso, sarà gettare questo errore. L'ho risolto non chiudendo il database finché non avessi completato tutto il lavoro di cui avevo bisogno.

62

Se hai scavare attraverso una notevole quantità di codice SQL si può essere in grado di velocizzare il debug mettendo il seguente frammento di codice nella vostra MainActivity per consentire StrictMode. Se vengono rilevati oggetti del database trapelati, l'app si bloccherà con le informazioni del registro evidenziando esattamente dove si trova la perdita. Questo mi ha aiutato a localizzare un cursore anomalo in pochi minuti.

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

È grandioso! Ho realizzato questo standard con 'if (BuildConfig.DEBUG) {...}' all'interno di un blocco 'static {...}' della mia classe principale. –

+1

Il modo migliore e più efficace per eseguire il debug di quel tipo di problema che ho trovato finora, dovrebbe essere la risposta accettata! – androidseb

+0

Ottimo! Ma in versione funzionerà senza 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

Dovresti aggiungere alcuni commenti che spiegano il tuo codice – sme

+0

Breakthrough CursorWindow limit di solo 2 megabyte –

Problemi correlati