Залп из-за ошибки памяти, странная попытка выделения

Иногда случайно 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:

См. Связанные проблемы в зеркале андроида-залпа:

Другие вопросы по тегам