Volley error de memoria, bash de asignación extraño

A veces, al azar, Volley bloquea mi aplicación al inicio, se bloquea en la clase de la aplicación y un usuario no podrá volver a abrirla hasta que entre en la configuración y borre los datos de la aplicación.

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) 

El “diskbasedbache” intenta asignar más de 1 gigabyte de memoria, sin ningún motivo obvio

¿Cómo haré que esto no suceda? Parece ser un problema con Volley, o tal vez un problema con un caché basado en disco personalizado, pero no veo inmediatamente (desde el seguimiento de la stack) cómo ‘borrar’ este caché o hacer una verificación condicional o manejar esta excepción

Insight apreciado

En el streamToBytes() , primero serán los nuevos bytes por la longitud del archivo de caché, ¿su archivo de caché es demasiado grande que el tamaño máximo de montón de la aplicación?

 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 desea borrar el caché, puede conservar la referencia de DiskBasedCache , después de que haya transcurrido el tiempo de disponibilidad, use ClearCacheRequest y pase esa instancia de caché en:

 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 esta forma se borrarán todos los cachés, por lo que le sugiero que lo use con cuidado. por supuesto, puede hacer una conditional check , simplemente iterando los archivos cacheDir , estimar cuál era demasiado grande y luego eliminarlo.

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

Volley no fue diseñado como una solución de caché de datos grandes, es un caché de solicitudes común, no almacena datos grandes en cualquier momento.

————- Actualización el 2014-07-17 ————-

De hecho, borrar todos los cachés es la forma final, tampoco es una forma inteligente, deberíamos suprimir estos caché de uso de solicitudes grandes cuando estamos seguros de que serían, y si no estamos seguros. aún podemos determinar el tamaño de los datos de respuesta, ya sea grande o no, y luego llamar a setShouldCache(false) para deshabilitarlo.

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

Experimenté el mismo problema.

Sabíamos que no teníamos archivos de tamaño GB en la inicialización de la memoria caché. También ocurrió al leer cadenas de encabezado, que nunca deben tener longitud de GB.

De modo que parecía que readLong estaba leyendo la longitud de forma incorrecta.

Teníamos dos aplicaciones con configuraciones más o menos idénticas, excepto que una aplicación tenía dos procesos independientes creados en el inicio. El proceso principal de la aplicación y un proceso ‘SyncAdapter’ siguiendo el patrón del adaptador de sincronización. Solo la aplicación con dos procesos se bloqueará. Estos dos procesos iniciarían de forma independiente el caché.

Sin embargo, DiskBasedCache utiliza la misma ubicación física para ambos procesos. Finalmente llegamos a la conclusión de que las inicializaciones simultáneas estaban dando lugar a lecturas simultáneas y escrituras de los mismos archivos, lo que lleva a malas lecturas del parámetro de tamaño.

No tengo una prueba completa de que este sea el problema, pero estoy planificando trabajar en una aplicación de prueba para verificar.

En el corto plazo, acabamos de captar la asignación de bytes demasiado grande en streamToBytes, y lanzamos una IOException para que Volley capture la excepción y simplemente elimine el archivo. Sin embargo, probablemente sería mejor usar un caché de disco por separado para cada proceso.

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