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