Unity通過WWW 類從persistentDataPath(或從網絡)加載圖片之內存暴增的問題

  • 問題描述

      在遊戲中過程中,我們可能需要動態的加載某一些圖片。這些圖片不一定要在build的時候放在包中,那麼就需要在可讀可寫的路徑persistentDataPath下加載,或者給一個網絡地址(URL)下載。無論採取哪種,需要用到Unity的WWW類來獲取。

      值的高興的是,www有一個關於如何獲取下載的圖片的API:www.texture字段和www.LoadImageIntoTexture(Texture2D)方法。所以如果不考慮其他未知的問題,以上兩個獲取圖片的方式都可以滿足我們的需求。但是仔細想想,既然這兩個方式都是殊途同歸,那麼Unity爲什麼這樣做呢?

     在我的項目中,我就入了這個坑。我是通過www.texture的方式獲取下載的圖片。那麼問題來了,當我下載並加載到內存中的圖片張數超過60時,Unity的Texture內存增加到550M,如果繼續加載,內存依次增加,直至手機內存不足而崩潰退出!!!!!

  • 兩種加載方式的分析
  1. 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的內存佔用是比較大的,如果在使用動態讀取加載圖片,那麼一定要注意這方面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章