Ich habe eine Routine, die viele Abfragen mehrmals pro Sekunde für eine SQLite-Datenbank ausführt. Nach einer Weile würde ich den Fehler bekommen
"android.database.CursorWindowAllocationException: - Cursor window allocation of 2048 kb failed. # Open Cursors = "
erscheinen in LogCat.
Ich hatte die Speicherauslastung des App-Protokolls, und wenn die Nutzung eine bestimmte Grenze erreicht, wird dieser Fehler angezeigt, was bedeutet, dass er nicht mehr verfügbar ist. Meine Intuition sagt mir, dass das Datenbankmodul jedes Mal, wenn ich eine Abfrage ausführe, einen NEUEN Puffer (CursorWindow) erstellt, und obwohl ich die Cursor .close () markiere, können weder der Garbage Collector noch SQLiteDatabase.releaseMemory()
schnell genug Speicher freigeben . Ich denke, die Lösung könnte darin bestehen, die Datenbank zu "zwingen", immer in denselben Puffer zu schreiben und keine neuen zu erstellen, aber ich konnte keinen Weg finden, dies zu tun. Ich habe versucht, mein eigenes CursorWindow zu instanziieren, und versucht, es ohne Erfolg auf SQLiteCursor zu setzen.
Irgendwelche Ideen?
BEARBEITEN: Beispielcodeanforderung von @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);
}
Idealerweise möchte ich in der Lage sein, .setWindow()
bevor ich eine neue Abfrage stelle, und die Daten CursorWindow
jedes Mal, wenn ich neue Daten erhalte , in die gleichen Daten einfügen.
CursorWindow
?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); }
Idealerweise möchte ich in der Lage sein, .setWindow zu erstellen, bevor ich eine neue Abfrage gebe, und die Daten jedes Mal, wenn ich neue Daten erhalte, in dasselbe CursorWindow einfügen.Antworten:
Die Ursache für diesen Fehler sind meistens nicht geschlossene Cursor. Stellen Sie sicher, dass Sie alle Cursor schließen, nachdem Sie sie verwendet haben (auch im Fehlerfall).
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(); }
Damit Ihre App abstürzt, wenn Sie keinen Cursor schließen, können Sie den Strikten Modus
detectLeakedSqlLiteObjects
in Ihren Anwendungen aktivierenonCreate
:Natürlich würden Sie dies nur für Debug-Builds aktivieren.
quelle
Wenn Sie eine erhebliche Menge an SQL-Code durchsuchen müssen, können Sie möglicherweise das Debuggen beschleunigen, indem Sie das folgende Codeausschnitt in Ihre MainActivity einfügen, um StrictMode zu aktivieren. Wenn durchgesickerte Datenbankobjekte erkannt werden, stürzt Ihre App jetzt mit Protokollinformationen ab, die genau angeben, wo sich Ihr Leck befindet. Dies half mir, innerhalb weniger Minuten einen Schurkencursor zu finden.
quelle
if (BuildConfig.DEBUG) {...}
einem 'statischen {...}' Block meiner Hauptklasse erstellt.Ich habe dieses Problem gerade erlebt - und die vorgeschlagene Antwort, den Cursor nicht zu schließen, während es gültig ist, war nicht, wie ich es behoben habe. Mein Problem war das Schließen der Datenbank, als SQLite versuchte, den Cursor neu zu füllen. Ich würde die Datenbank öffnen, die Datenbank abfragen, um einen Cursor auf einen Datensatz zu erhalten, die Datenbank schließen und über den Cursor iterieren. Ich bemerkte, dass meine App immer dann mit demselben Fehler im OP abstürzte, wenn ich einen bestimmten Datensatz in diesem Cursor traf.
Ich gehe davon aus, dass der Cursor für den Zugriff auf bestimmte Datensätze die Datenbank erneut abfragen muss. Wenn er geschlossen wird, wird dieser Fehler ausgegeben. Ich habe das Problem behoben, indem ich die Datenbank erst geschlossen habe, nachdem ich alle erforderlichen Arbeiten abgeschlossen hatte.
quelle
Es gibt tatsächlich eine maximale Größe, die Android SQLite-Cursorfenster annehmen können, und das sind 2 MB. Alles, was größer als diese Größe ist, würde zu dem obigen Fehler führen. Meistens wird dieser Fehler entweder durch ein großes Bildbyte-Array verursacht, das als Blob in der SQL-Datenbank gespeichert ist, oder durch zu lange Zeichenfolgen. Hier ist, wie ich es behoben habe.
Erstellen Sie eine Java-Klasse, z. FixCursorWindow und geben Sie den folgenden Code ein.
Gehen Sie nun zu Ihrer Anwendungsklasse (erstellen Sie eine, falls Sie dies noch nicht getan haben) und rufen Sie das FixCursorWindow wie folgt auf
öffentliche Klasse App erweitert Anwendung {
}}
Stellen Sie schließlich sicher, dass Sie Ihre Anwendungsklasse wie folgt in Ihr Manifest auf dem Anwendungs-Tag aufnehmen
Das ist alles, es sollte jetzt perfekt funktionieren.
quelle
Wenn Sie Android P ausführen, können Sie Ihr eigenes Cursorfenster wie folgt erstellen:
Auf diese Weise können Sie die Größe des Cursorfensters für einen bestimmten Cursor ändern, ohne auf Reflexionen zurückgreifen zu müssen.
quelle
Hier ist die Antwort von @whlk mit der automatischen Ressourcenverwaltung von Java 7 für den Try-finally-Block:
try (Cursor cursor = db.query(...)) { // do some work with the cursor here. }
quelle
Dies ist eine normale Ausnahme, wenn wir insbesondere externes SQLite verwenden. Sie können das Problem beheben, indem Sie das Cursorobjekt wie folgt schließen:
Dies bedeutet, dass der Cursor über Speicher verfügt und geöffnet und dann geschlossen wird, damit die Anwendung schneller wird, alle Methoden weniger Platz beanspruchen und die mit der Datenbank verbundenen Funktionen ebenfalls verbessert werden.
quelle
quelle