wake-up-neo.com

SQLite Android Zuweisung des Datenbankcursorfensters von 2048 KB fehlgeschlagen

Ich habe eine Routine, die verschiedene Abfragen für eine SQLite-Datenbank viele Male pro Sekunde ausführt. Nach einer Weile würde ich den Fehler bekommen

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

Ich hatte die Speicherauslastung des App-Protokolls und tatsächlich, wenn die Auslastung eine bestimmte Grenze erreicht, erhalte ich diesen Fehler, was bedeutet, dass er ausgeht. Meiner Intuition nach erstellt das Datenbankmodul bei jeder Abfrage einen NEUEN Puffer (CursorWindow), und obwohl ich die Option .close () als Cursor markiere, sind weder der Garbage Collector noch SQLiteDatabase.releaseMemory() schnell genug, um Speicher freizugeben. Ich denke, die Lösung könnte darin bestehen, die Datenbank zu "zwingen", immer in den gleichen Puffer zu schreiben und keine neuen zu erstellen, aber ich habe keinen Weg gefunden, dies zu tun. Ich habe versucht, mein eigenes CursorWindow zu instanziieren, und versucht, es auf und SQLiteCursor zu setzen, ohne Erfolg.

Irgendwelche Ideen?

BEARBEITEN: Beispiel für eine Codeanforderung 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);
}

Im Idealfall möchte ich in der Lage sein, .setWindow() zu können, bevor ich eine neue Abfrage mache, und die Daten jedes Mal, wenn ich neue Daten erhalte, in dieselbe CursorWindow einfügen zu lassen.

64
alex

Die häufigste Ursache für diesen Fehler sind 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();
}
99
whlk

Wenn Sie eine erhebliche Menge an SQL-Code durchsuchen müssen, können Sie das Debuggen möglicherweise beschleunigen, indem Sie den folgenden Codeausschnitt in Ihre MainActivity einfügen, um StrictMode zu aktivieren. Wenn durchgesickerte Datenbankobjekte erkannt werden, stürzt Ihre App jetzt ab, wobei die Protokollinformationen genau angeben, wo sich Ihr Leck befindet. Dies half mir, einen Schurkencursor in wenigen Minuten zu finden.

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

Ich habe dieses Problem gerade erlebt - und die vorgeschlagene Antwort, den Cursor nicht zu schließen, während er gültig ist, war nicht so, 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 bekommen, die Datenbank schließen und über den Cursor iterieren. Ich habe festgestellt, dass meine App bei jedem Treffer eines bestimmten Datensatzes in diesem Cursor mit demselben Fehler in OP abstürzt.

Ich gehe davon aus, dass der Cursor, um auf bestimmte Datensätze zuzugreifen, 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 ausgeführt hatte.

11
micnguyen

Es gibt in der Tat eine maximale Größe, die Android SQLite-Cursor-Fenster benötigen können, und das sind 2 MB. Alles, was größer ist, würde zu dem obigen Fehler führen. Meistens wird dieser Fehler entweder durch ein großes Bildbyte verursacht Array als Blob in SQL-Datenbank oder zu lange Zeichenfolgen gespeichert. Hier ist, wie ich es behoben.

Erstellen Sie eine Java class zB FixCursorWindow und fügen Sie den folgenden Code ein.

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

Wechseln Sie nun zu Ihrer Anwendungsklasse (erstellen Sie eine, falls noch nicht geschehen) und rufen Sie das FixCursorWindow folgendermaßen auf

public class App erweitert Anwendung {

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

}

}

Stellen Sie schließlich sicher, dass Ihre Anwendungsklasse in Ihrem Manifest auf dem Anwendungstag wie folgt enthalten ist

    Android:name=".App">

Das ist alles, es sollte jetzt perfekt funktionieren.

3

Wenn Sie Android P ausführen, können Sie Ihr eigenes Cursorfenster wie folgt erstellen:

if(cursor instanceof SQLiteCursor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    ((SQLiteCursor) cursor).setWindow(new CursorWindow(null, 1024*1024*10));
}

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.

0