【问题标题】:Volley out of memory error, weird allocation attemptVolley 内存不足错误,奇怪的分配尝试
【发布时间】:2014-07-28 13:19:04
【问题描述】:

有时 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)

“diskbasedbach”尝试分配超过 1 GB 的内存,原因不明

我怎样才能避免这种情况发生?这似乎是 Volley 的问题,或者可能是基于自定义磁盘的缓存的问题,但我没有立即看到(从堆栈跟踪)如何“清除”此缓存或进行条件检查或处理此异常

洞察力赞赏

【问题讨论】:

    标签: android out-of-memory android-volley diskcache


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      我遇到了同样的问题。

      我们知道在缓存初始化时我们没有 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;
      }
      

      【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 好的,所以我可以在应用程序类首次打开时或在我的主要活动中清除此缓存。我必须检查基于磁盘的缓存文件
      • 这很有帮助,但结果是我的一个第三方库也在初始化一个新的 Volley 请求,我让他们更改了他们的库
      • 如果你确定某些请求会占用大量数据,你可以通过Request.setShouldCache(false)方法抑制这些请求,不要缓存它。
      • 我应该清除应用程序类或我的活动中的缓存吗?我应该把这段代码放在哪里?
      • 正如我之前所说,似乎 DiskBasedCache.streamToBytes() 试图分配巨大的内存,从而导致此错误。我建议您调试(即阻止)该方法,检查 length 是什么,还要弄清楚为什么这个长度很大。
      猜你喜欢
      • 1970-01-01
      • 2018-05-26
      • 2017-04-14
      • 1970-01-01
      • 2019-11-23
      • 2011-01-11
      • 2019-12-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多