2014-06-07 58 views
17

有時隨機啓動時,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 GB內存,沒有明顯的理由

我會怎麼做這不會發生?這似乎是Volley的問題,也可能是基於自定義磁盤的緩存問題,但我並不立即看到(從堆棧跟蹤)如何「清除」此緩存或進行有條件檢查或處理此異常

洞察鑑賞

回答

15

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年7月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); 
     ... 
    } 
} 
+0

好,因此應用程序類第一次打開時,我可以清除該緩存,或者在我的主要活動..我得檢查基於磁盤的緩存文件 – CQM

+0

這是有益的,但事實證明,一個我的第三方庫也正在初始化一個新的Volley請求,我讓他們改變他們的庫 – CQM

+1

如果你確定某些請求會佔用大量數據,你可以通過Request.setShouldCache(false)來抑制這些請求不要緩存它。方法。 – VinceStyling

3

我遇到了同樣的問題。

我們知道我們在緩存初始化時沒有大小爲GB的文件。讀取標題字符串時也會出現這種情況,字符串永遠不應該是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; 
} 
+0

而不是嘗試從OOME中恢復,您可以拒絕大於緩存大小(或負數)的長度。默認的DiskBasedCache大小隻有5MB。請注意,其他人報告了從相同位置拋出的NegativeArraySizeException:https://code.google.com/p/android/issues/detail?id=209471 –

+0

以下是android-volley鏡像中的相關問題:https:// github .com/mcxiaoke/android-volley/issues/37 https://github.com/mcxiaoke/android-volley/issues/61 –