- 問題描述
在遊戲中過程中,我們可能需要動態的加載某一些圖片。這些圖片不一定要在build的時候放在包中,那麼就需要在可讀可寫的路徑persistentDataPath下加載,或者給一個網絡地址(URL)下載。無論採取哪種,需要用到Unity的WWW類來獲取。
值的高興的是,www有一個關於如何獲取下載的圖片的API:www.texture字段和www.LoadImageIntoTexture(Texture2D)方法。所以如果不考慮其他未知的問題,以上兩個獲取圖片的方式都可以滿足我們的需求。但是仔細想想,既然這兩個方式都是殊途同歸,那麼Unity爲什麼這樣做呢?
在我的項目中,我就入了這個坑。我是通過www.texture的方式獲取下載的圖片。那麼問題來了,當我下載並加載到內存中的圖片張數超過60時,Unity的Texture內存增加到550M,如果繼續加載,內存依次增加,直至手機內存不足而崩潰退出!!!!!
- 兩種加載方式的分析
- www.texture
官方說明:Returns a Texture2D generated from the downloaded data (Read Only).保存了下載後的圖片,以Unity中的Texture2D格式返回給調用者。
如果下載一張圖片,用www.texture的方式獲取,在使用了一段時間後,將該圖片Destroy掉,但在內存中該圖仍然存在,
各位請看加載圖片的代碼:
public IEnumerator LoadTexture(string name,RawImage spriteReceiver, System.Action onLoadedSuccess = null, System.Action onLoadedFailed = null)
{
if(isLoaded(name))
{
yield break;
// use loaded texture
}
string url = "file:///" + Application.persistentDataPath + "/" + name + ".png";
WWW www = new WWW(url);
while (!www.isDone)
{
yield return null;
}
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log(name + www.error);
// download from web service
}else
{
Debug.Log("load from local");
Texture2D t2d = new Texture2D(1, 1);
LoadedSprite lsp = new LoadedSprite(topicId_index, t2d);
GraySprites.Enqueue(lsp);
t2d = www.texture;
spriteReceiver.texture = t2d;
spriteReceiver.texture.name = name;
onLoadedSuccess?.Invoke();
}
www.Dispose();
www = null;
trim(GraySprites);
}
private void trim(Queue<LoadedSprite> queue)
{
if (queue.Count > 60)
{
LoadedSprite sp = queue.Dequeue();
DestroyImmediate(sp.texture);
}
}
問題是,數量大於60後,內存沒有隨着DestroyImmediate(texture)的調用而保持平穩水平,而是一直增加。
詳細閱讀代碼,發現這四句代碼是問題的關鍵,並將代碼:t2d = www.texture;緊隨放在new Texture(1,1);之後,問題即得到解決,內存在數量大於60之後維持在一定水平不再增加。
修改後的代碼如下:
public IEnumerator LoadTexture(string name,RawImage spriteReceiver, System.Action onLoadedSuccess = null, System.Action onLoadedFailed = null)
{
if(isLoaded(name))
{
yield break;
// use loaded texture
}
string url = "file:///" + Application.persistentDataPath + "/" + name + ".png";
WWW www = new WWW(url);
while (!www.isDone)
{
yield return null;
}
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log(name + www.error);
// download from web service
}else
{
Debug.Log("load from local");
Texture2D t2d = new Texture2D(1, 1);
t2d = www.texture;
LoadedSprite lsp = new LoadedSprite(topicId_index, t2d);
GraySprites.Enqueue(lsp);
spriteReceiver.texture = t2d;
spriteReceiver.texture.name = name;
onLoadedSuccess?.Invoke();
}
www.Dispose();
www = null;
trim(GraySprites);
}
private void trim(Queue<LoadedSprite> queue)
{
if (queue.Count > 60)
{
LoadedSprite sp = queue.Dequeue();
DestroyImmediate(sp.texture);
}
}
2.www.LoadImageIntoTexture(Texture2d)
官方說明:Replaces the contents of an existing Texture2D with an image from the downloaded data.將下載後的圖片數據寫到一張已存在的Texture2d 圖片中,這張圖片原來的數據會被覆蓋。
通過這種方式加載圖片的代碼如下:
public IEnumerator LoadTexture(string name,RawImage spriteReceiver, System.Action onLoadedSuccess = null, System.Action onLoadedFailed = null)
{
if(isLoaded(name))
{
yield break;
// use loaded texture
}
string url = "file:///" + Application.persistentDataPath + "/" + name + ".png";
WWW www = new WWW(url);
while (!www.isDone)
{
yield return null;
}
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log(name + www.error);
// download from web service
}else
{
Debug.Log("load from local");
Texture2D t2d = new Texture2D(1, 1);
LoadedSprite lsp = new LoadedSprite(topicId_index, t2d);
www.LoadImageIntoTexture(t2d);
GraySprites.Enqueue(lsp);
spriteReceiver.texture = t2d;
spriteReceiver.texture.name = name;
onLoadedSuccess?.Invoke();
}
www.Dispose();
www = null;
trim(GraySprites);
}
private void trim(Queue<LoadedSprite> queue)
{
if (queue.Count > 60)
{
LoadedSprite sp = queue.Dequeue();
DestroyImmediate(sp.texture);
}
}
這裏,變量t2d是在www.LoadImageIntoTexture(t2d);這裏賦的值。運行這段代碼,加載數量大於60之後,內存維持在一定水平而不再增加。
一開始,我以爲是我寫代碼不注意,對變量t2d的申明、賦值和使用順序有問題(事實卻是如此,t2d = www.texture;方式必須要要先賦值,再使用;但是www.LoadImageIntoTexture(t2d);方式卻並沒有這種限制,我們可以在這段代碼之前,將t2d變量作爲引用類型進行使用是沒問題的),仔細思考,既然t2d是引用類型,那麼t2d = www.texture;出現的順序和www.LoadImageIntoTexture(t2d);一樣,但是得到的結果不一樣:前者並沒有將Texture保存到列表中,成爲了UnUsedAssets,失去了引用,要想釋放內存,只能手動調用Resources.UnloadUnusedAssets();;而後者卻將Texture放在t2d中並保存到列表中。
- 問題的討論
個人認爲,還是因爲自己寫代碼的不規範導致的問題,如果按照正常的邏輯寫,是不會出現這個問題的。但是順序變換了,在我們的思維中,邏輯也是沒問題的,但是,Unity中,關於t2d = www.texture和www.LoadImageIntoTexture(t2d);的理解是不一樣的。LoadedSprite lsp = new LoadedSprite(topicId_index, www.texture);這樣的代碼是沒有問題的,問題出在了中間多了一個t2d變量。(繞死我了!!!!!)如果您對此有更好的看法,歡迎不吝賜教。
最後,Unity中一張Texture的內存佔用是比較大的,如果在使用動態讀取加載圖片,那麼一定要注意這方面。