web-dev-qa-db-ja.com

ボレーのメモリ不足エラー、奇妙な割り当ての試み

起動時に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の問題、またはカスタムディスクベースのキャッシュの問題のようですが、このキャッシュを「クリア」する方法、条件付きチェックを実行する方法、またはこの例外を処理する方法が(スタックトレースから)すぐにはわかりません。

洞察力を高く評価

17
CQM

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);
        ...
    }
}
_
15
VinceStyling

私は同じ問題を経験しました。

キャッシュの初期化時にサイズがGBのファイルがないことはわかっていました。また、ヘッダー文字列を読み取るときにも発生しました。ヘッダー文字列の長さはGBであってはなりません。

そのため、readLongによって長さが誤って読み取られているように見えました。

1つのアプリの起動時に2つの独立したプロセスが作成されたことを除いて、セットアップがほぼ同じ2つのアプリがありました。メインアプリケーションプロセスと、同期アダプタパターンに従った「SyncAdapter」プロセス。 2つのプロセスを持つアプリのみがクラッシュします。これらの2つのプロセスは、独立してキャッシュを初期化します。

ただし、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
HannahMitt

問題が発生すると、それ以降の初期化のたびに再発し、無効なキャッシュヘッダーを指しているようです。

幸い、この問題は公式のVolleyリポジトリで修正されています。

Androidボレーミラーの関連する問題を参照してください。

1
Joe Bowbeer