J'ai une routine qui exécute différentes requêtes sur une base de données SQLite plusieurs fois par seconde. Après un certain temps, j'obtiendrais l'erreur
"Android.database.CursorWindowAllocationException: - Cursor window allocation of 2048 kb failed. # Open Cursors = "
Apparaît dans LogCat.
J'ai eu l'utilisation de la mémoire du journal de l'application, et en effet, lorsque l'utilisation atteint une certaine limite, j'obtiens cette erreur, ce qui implique qu'elle s'épuise. Mon intuition me dit que le moteur de base de données crée un nouveau tampon (CursorWindow) chaque fois que j'exécute une requête, et même si je marque le .close () les curseurs, ni le garbage collector ni SQLiteDatabase.releaseMemory()
ne sont rapides assez pour libérer de la mémoire. Je pense que la solution peut consister à "forcer" la base de données à toujours écrire dans le même tampon et à ne pas en créer de nouvelles, mais je n'ai pas réussi à trouver un moyen de le faire. J'ai essayé d'instancier mon propre CursorWindow, et j'ai essayé de le définir et de SQLiteCursor en vain.
Des idées?
EDIT: exemple de demande de code 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);
}
Idéalement, je voudrais pouvoir .setWindow()
avant de donner une nouvelle requête, et avoir les données mises dans le même CursorWindow
chaque fois que j'obtiens de nouvelles données.
Le plus souvent, la cause de cette erreur sont des curseurs non fermés. Assurez-vous de fermer tous les curseurs après les avoir utilisés (même en cas d'erreur).
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 vous devez fouiller dans une quantité importante de code SQL, vous pourrez peut-être accélérer votre débogage en plaçant l'extrait de code suivant dans votre MainActivity pour activer StrictMode. Si des objets de base de données ayant fait l'objet d'une fuite sont détectés, votre application se bloque désormais avec les informations de journal qui mettent en évidence exactement où se trouve votre fuite. Cela m'a aidé à localiser un curseur escroc en quelques minutes.
@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate(savedInstanceState);
...
...
Je viens de rencontrer ce problème - et la réponse suggérée de ne pas fermer le curseur alors qu'il était valide, n'était pas comme je l'ai résolu. Mon problème était la fermeture de la base de données lorsque SQLite essayait de repeupler son curseur. Je voudrais ouvrir la base de données, interroger la base de données pour obtenir un curseur sur un ensemble de données, fermer la base de données et parcourir le curseur. J'ai remarqué que chaque fois que j'atteignais un certain enregistrement dans ce curseur, mon application se bloquait avec cette même erreur dans OP.
Je suppose que pour que le curseur accède à certains enregistrements, il doit réinterroger la base de données et s'il est fermé, il générera cette erreur. Je l'ai corrigé en ne fermant pas la base de données avant d'avoir terminé tout le travail dont j'avais besoin.
Il y a en effet une taille maximale Android Les fenêtres de curseur SQLite peuvent prendre et qui est de 2 Mo, tout ce qui dépasse cette taille entraînerait l'erreur ci-dessus. La plupart du temps, cette erreur est soit causée par un grand octet d'image tableau stocké sous forme de blob dans la base de données SQL ou des chaînes trop longues. Voici comment je l'ai corrigé.
Créez une classe Java par exemple FixCursorWindow et mettez le code ci-dessous.
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();
}
}
Allez maintenant dans votre classe d'application (créez-en une si vous ne l'avez pas déjà fait) et appelez le FixCursorWindow comme ceci
application de classe publique étend l'application {
public void onCreate()
{
super.onCreate();
CursorWindowFixer.fix();
}
}
Enfin, assurez-vous d'inclure votre classe d'application dans votre manifeste sur la balise d'application comme ceci
Android:name=".App">
C'est tout, cela devrait fonctionner parfaitement maintenant.
Si vous exécutez Android P, vous pouvez créer votre propre fenêtre de curseur comme ceci:
if(cursor instanceof SQLiteCursor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
((SQLiteCursor) cursor).setWindow(new CursorWindow(null, 1024*1024*10));
}
Cela vous permet de modifier la taille de la fenêtre du curseur pour un curseur spécifique sans recourir aux réflexions.