Ошибка воли за пределами памяти, странная попытка размещения
Иногда случайным образом волейбол сбрасывает мое приложение при запуске, он падает в классе приложения, и пользователь не сможет снова открыть приложение, пока не войдет в настройки и не очистит данные приложения.
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 гигабайта памяти, без видимой причины
как я могу сделать это не так? Похоже, что проблема связана с Volley, или, может быть, с кэшем на основе диска, но я не вижу сразу (из трассировки стека), как "очистить" этот кеш или выполнить условную проверку или обработать это исключение.
Проницательность оценена
Ответы
Ответ 1
В 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);
...
}
}
Ответ 2
У меня возникла одна и та же проблема.
Мы знали, что при инициализации кеша у нас не было файлов размером в 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;
}
Ответ 3
Как только проблема возникает, она, кажется, повторяется при каждой последующей инициализации, указывая на недопустимый кешированный заголовок.
К счастью, эта проблема была исправлена в официальном репетиторе Volley:
См. связанные проблемы в зеркале Android-волейбола: