web-dev-qa-db-de.com

Volley out of memory error, seltsamer Zuweisungsversuch

Manchmal stürzt Volley meine App beim Start nach dem Zufallsprinzip ab, es stürzt in der Anwendungsklasse ab und ein Benutzer kann die App erst wieder öffnen, wenn er in die Einstellungen wechselt und die App-Daten löscht

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)

Der "diskbasedbache" versucht, ohne ersichtlichen Grund mehr als 1 GB Speicher zuzuweisen

wie würde ich das verhindern? Es scheint ein Problem mit Volley zu sein, oder vielleicht ein Problem mit einem benutzerdefinierten festplattenbasierten Cache, aber ich sehe (aus dem Stack-Trace) nicht sofort, wie dieser Cache "geleert" oder eine bedingte Prüfung durchgeführt oder diese Ausnahme behandelt werden kann

Einblick geschätzt

17
CQM

In der streamToBytes() werden zuerst neue Bytes um die Länge der Cache-Datei hinzugefügt. War Ihre Cache-Datei zu groß als die maximale Größe des Anwendungs-Heapspeichers?

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

Wenn Sie den Cache leeren möchten, können Sie den Verweis auf DiskBasedCache beibehalten, nachdem die Löschzeit abgelaufen ist, ClearCacheRequest verwenden und diese Cache-Instanz übergeben in:

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

auf diese Weise werden alle Caches gelöscht. Ich empfehle daher, dass Sie vorsichtig damit umgehen. Natürlich können Sie conditional check ausführen, indem Sie einfach die cacheDir - Dateien iterieren. Schätzen Sie, welche zu groß war, und entfernen Sie sie dann.

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

Volley war nicht als Big-Data-Cache-Lösung konzipiert, sondern als allgemeiner Anforderungscache, der zu keinem Zeitpunkt große Datenmengen speichert.

------------- Update am 17.07.2014 -------------

In der Tat, löschen Sie alle Caches ist der letzte Weg, auch ist nicht klug Weg, sollten wir diese große Anfrage verwenden Cache zu unterdrücken, wenn wir sicher, dass es wäre, und wenn nicht sicher? Wir können immer noch die Größe der Antwortdaten bestimmen, ob sie groß sind oder nicht. Rufen Sie dann setShouldCache(false) auf, um sie zu deaktivieren.

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

Ich habe das gleiche Problem erlebt.

Wir wussten, dass wir bei der Initialisierung des Caches keine Dateien hatten, die GB groß waren. Es trat auch beim Lesen von Header-Strings auf, die niemals GB lang sein sollten.

Es sah also so aus, als würde die Länge von readLong falsch gelesen.

Wir hatten zwei Apps mit ungefähr identischen Setups, mit der Ausnahme, dass bei einer App beim Start zwei unabhängige Prozesse erstellt wurden. Der Hauptanwendungsprozess und ein 'SyncAdapter'-Prozess, der dem Synchronisationsadaptermuster folgt. Nur die App mit zwei Prozessen würde abstürzen. Diese beiden Prozesse würden den Cache unabhängig voneinander initialisieren.

Der DiskBasedCache verwendet jedoch für beide Prozesse denselben physischen Speicherort. Wir kamen schließlich zu dem Schluss, dass gleichzeitige Initialisierungen zu gleichzeitigen Lese- und Schreibvorgängen derselben Dateien führten, was zu fehlerhaften Lesevorgängen des Größenparameters führte.

Ich habe keinen vollständigen Beweis, dass dies das Problem ist, aber ich plane, an einer Test-App zu arbeiten, um dies zu überprüfen.

Auf kurze Sicht haben wir gefangen nur die zu große Byte-Zuordnung in streamToBytes und eine IOException werfen, so dass Volley die Ausnahme abfängt und löscht einfach die Datei. Es ist jedoch wahrscheinlich besser, für jeden Prozess einen separaten Festplatten-Cache zu verwenden.

 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

Sobald das Problem auftritt, scheint es bei jeder nachfolgenden Initialisierung erneut aufzutreten und auf einen ungültigen zwischengespeicherten Header zu verweisen.

Glücklicherweise wurde dieses Problem im offiziellen Volley-Repo behoben:

Siehe verwandte Probleme im Android-Volley-Spiegel:

1
Joe Bowbeer