2014-06-07 42 views
17

有时随机启动时,Volley会崩溃我的应用程序,它将在应用程序类中崩溃,并且用户将无法再次打开该应用程序,直到它们进入设置并清除应用程序数据凌乱的内存错误,奇怪的分配尝试

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”试图分配超过1 GB内存,没有明显的理由

我会怎么做这不会发生?这似乎是Volley的问题,也可能是基于自定义磁盘的缓存问题,但我并不立即看到(从堆栈跟踪)如何“清除”此缓存或进行有条件检查或处理此异常

洞察鉴赏

回答

15

streamToBytes(),首先它会由缓存文件长度的新字节,你的缓存文件是否比应用程序的最大堆大小?

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

如果要清除高速缓存中,你可以保持DiskBasedCache参考,清除后的时间的来了,用ClearCacheRequest并传递缓存实例中:

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

这样将清除所有缓存,所以我建议你仔细使用它。当然,你可以做conditional check,只是迭代cacheDir文件,估计它太大,然后删除它。

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

Volley不是作为大数据缓存解决方案设计的,它是常见的请求缓存,不随时存储大数据。

-------------更新于2014年7月17日-------------

事实上,清除所有的缓存是最终的方式,也不是明智的做法,我们应该在确定它的时候抑制这些大的请求使用缓存,并且如果不确定的话?我们仍然可以确定响应数据大小与否,然后致电setShouldCache(false)将其禁用。

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); 
     ... 
    } 
} 
+0

好,因此应用程序类第一次打开时,我可以清除该缓存,或者在我的主要活动..我得检查基于磁盘的缓存文件 – CQM

+0

这是有益的,但事实证明,一个我的第三方库也正在初始化一个新的Volley请求,我让他们改变他们的库 – CQM

+1

如果你确定某些请求会占用大量数据,你可以通过Request.setShouldCache(false)来抑制这些请求不要缓存它。方法。 – VinceStyling

3

我遇到了同样的问题。

我们知道我们在缓存初始化时没有大小为GB的文件。读取标题字符串时也会出现这种情况,字符串永远不应该是GB。

所以它看起来像readLong正在读取的长度不正确。

我们有两个应用程序的设置大致相同,除了一个应用程序在启动时创建了两个独立的进程。主要的应用程序进程和一个跟随同步适配器模式的'SyncAdapter'进程。只有具有两个进程的应用程序才会崩溃。 这两个进程将独立初始化缓存。

但是,DiskBasedCache为两个进程使用相同的物理位置。我们最终得出结论:并发初始化导致并发读取和写入相同的文件,导致大小参数的读取不正确。

我没有充分证明这是问题,但我打算在测试应用程序上进行验证。

在短期内,我们刚刚捕获了streamToBytes中过大的字节分配,并抛出IOException,以便Volley捕获异常并删除文件。 但是,对每个进程使用单独的磁盘缓存可能会更好。

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

而不是尝试从OOME中恢复,您可以拒绝大于缓存大小(或负数)的长度。默认的DiskBasedCache大小只有5MB。请注意,其他人报告了从相同位置抛出的NegativeArraySizeException:https://code.google.com/p/android/issues/detail?id=209471 –

+0

以下是android-volley镜像中的相关问题:https:// github .com/mcxiaoke/android-volley/issues/37 https://github.com/mcxiaoke/android-volley/issues/61 –