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

洞察力赞赏

4

3 回答 3

16

在中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);
        ...
    }
}
于 2014-06-26T04:27:03.553 回答
3

我遇到了同样的问题。

我们知道在缓存初始化时我们没有 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;
}
于 2016-06-29T17:09:14.457 回答
1

一旦出现问题,它似乎会在每次后续初始化时再次出现,指向无效的缓存标头。

幸运的是,这个问题已在 Volley 官方 repo 中修复:

在 android-volley 镜像中查看相关问题:

于 2017-02-13T05:02:53.197 回答