web-dev-qa-db-fra.com

Erreur de mémoire insuffisante, tentative d'allocation bizarre

Parfois, de manière aléatoire, Volley bloque mon application au démarrage, il se bloque dans la classe d'applications et un utilisateur ne peut plus l'ouvrir à nouveau avant d'avoir défini les paramètres et effacé les données de l'application.

Java.lang.OutOfMemoryError
at com.Android.volley.toolbox.DiskBasedCache.streamToBytes(DiskBasedCache.Java:316)
at com.Android.volley.toolbox.DiskBasedCache.readString(DiskBasedCache.Java:526)
at com.Android.volley.toolbox.DiskBasedCache.readStringStringMap(DiskBasedCache.Java:549)
at com.Android.volley.toolbox.DiskBasedCache$CacheHeader.readHeader(DiskBasedCache.Java:392)
at com.Android.volley.toolbox.DiskBasedCache.initialize(DiskBasedCache.Java:155)
at com.Android.volley.CacheDispatcher.run(CacheDispatcher.Java:84)

"Diskbasedbache" tente d'allouer plus d'1 gigaoctet de mémoire, sans raison évidente

comment pourrais-je faire en sorte que cela ne se produise pas? Cela semble être un problème avec Volley, ou peut-être un problème avec un cache basé sur un disque personnalisé, mais je ne vois pas immédiatement (à partir de la trace de la pile) comment «effacer» ce cache, effectuer un contrôle conditionnel ou gérer cette exception.

Insight apprécié

17
CQM

Dans streamToBytes(), tout d'abord, il créera de nouveaux octets en fonction de la longueur du fichier de cache. Votre fichier de cache est-il trop volumineux par rapport à la taille de segment de mémoire maximale de l'application?

private static byte[] streamToBytes(InputStream in, int length) throws IOException {
    byte[] bytes = new byte[length];
    ...
}

public synchronized Entry get(String key) {
    CacheHeader entry = mEntries.get(key);

    File file = getFileForKey(key);
    byte[] data = streamToBytes(..., file.length());
}

Si vous souhaitez effacer le cache, vous pouvez conserver la référence DiskBasedCache. Une fois l'heure de l'effacement écoulée, utilisez ClearCacheRequest et transmettez cette instance de cache dans:

File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
DiskBasedCache cache = new DiskBasedCache(cacheDir);
RequestQueue queue = new RequestQueue(cache, network);
queue.start();

// clear all volley caches.
queue.add(new ClearCacheRequest(cache, null));

de cette façon, toutes les caches seront effacées, je vous suggère donc de les utiliser avec précaution. bien sûr, vous pouvez faire conditional check, simplement en itérant le fichier cacheDir , estimez ce qui était trop gros, puis supprimez-le.

for (File cacheFile : cacheDir.listFiles()) {
    if (cacheFile.isFile() && cacheFile.length() > 10000000) cacheFile.delete();
}

Volley n'était pas conçu comme une solution de cache de données volumineuses, c'est un cache de requêtes commun, ne stockez pas de données volumineuses à tout moment.

------------- Mise à jour au 2014-07-17 -------------

En fait, effacer tous les caches est le dernier moyen, pas plus sage, nous devrions supprimer ces caches d’utilisation volumineuse lorsque nous en sommes sûrs, et si nous n’en sommes pas sûr? nous pouvons toujours déterminer si la taille des données de réponse est grande ou non, puis appelez setShouldCache(false) pour le désactiver.

public class TheRequest extends Request {
    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        // if response data was too large, disable caching is still time.
        if (response.data.length > 10000) setShouldCache(false);
        ...
    }
}
15
VinceStyling

J'ai vécu le même problème.

Nous savions que nous n'avions pas de fichiers de taille Go lors de l'initialisation du cache. Cela se produisait également lors de la lecture de chaînes d'en-tête, qui ne devraient jamais dépasser GB.

Il semblait donc que readLong lisait de manière incorrecte la longueur.

Nous avions deux applications avec des configurations à peu près identiques, sauf qu'une application avait deux processus indépendants créés au démarrage. Le processus d'application principal et un processus 'SyncAdapter' qui suit le modèle d'adaptateur de synchronisation. Seule l'application avec deux processus se bloquerait. Ces deux processus initialiseraient indépendamment le cache.

Cependant, DiskBasedCache utilise le même emplacement physique pour les deux processus. Nous avons finalement conclu que les initialisations simultanées entraînaient des lectures et des écritures simultanées des mêmes fichiers, entraînant de mauvaises lectures du paramètre size.

Je n'ai pas une preuve complète que ce soit le problème, mais je prévois de travailler sur une application de test pour vérifier.

À court terme, nous venons de détecter l’allocation d’octets trop importante dans streamToBytes et de lancer une exception IOException afin que Volley capture l’exception et supprime simplement le fichier. Cependant, il serait probablement préférable d'utiliser un cache de disque distinct pour chaque processus.

 private static byte[] streamToBytes(InputStream in, int length) throws IOException {
    byte[] bytes;

    // this try-catch is a change added by us to handle a possible multi-process issue when reading cache files
    try {
        bytes = new byte[length];
    } catch (OutOfMemoryError e) {
        throw new IOException("Couldn't allocate " + length + " bytes to stream. May have parsed the stream length incorrectly");
    }

    int count;
    int pos = 0;
    while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) {
        pos += count;
    }
    if (pos != length) {
        throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
    }
    return bytes;
}
3
HannahMitt

Une fois que le problème survient, il semble se reproduire à chaque initialisation suivante, en pointant sur un en-tête mis en cache non valide.

Heureusement, ce problème a été résolu dans le rapport officiel Volley:

Voir les problèmes liés dans le miroir Android-volley:

1
Joe Bowbeer