繁体   English   中英

Volley内存不足错误,奇怪的分配尝试

[英]Volley out of memory error, weird allocation attempt

有时,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似乎有问题,或者基于定制磁盘的缓存可能有问题,但我没有立即(从堆栈跟踪中)看到如何“清除”此缓存或进行条件检查或处理此异常

洞察力赞赏

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

我遇到了同样的问题。

我们知道在初始化缓存时没有大小为GB的文件。 在读取标头字符串时也会发生这种情况,该标头字符串的长度不得超过GB。

因此,看起来好像是readLong错误读取了长度。

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

但是,DiskBasedCache对两个进程使用相同的物理位置。 我们最终得出结论,并发初始化导致对同一文件的并发读取和写入,从而导致对size参数的错误读取。

我没有充分的证据证明这是问题所在,但我打算使用测试应用进行验证。

在短期内,我们刚刚在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;
}

一旦出现问题,它似乎会在随后的每次初始化中重现,指向无效的缓存头。

幸运的是,此问题已在官方Volley回购中修复:

请参阅android-volley镜像中的相关问题:

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM