Unity3D學習(十一):關於UI銷毀后圖集仍然無法釋放問題的解決辦法


前言

最近進行項目性能優化的時候發現的問題。

問題

從大廳進到單局的過程中,會經過選擇英雄和加載兩個流程,這兩個流程對應的UI界面都會有一張幾mb左右的貼圖作為背景,在進入單局游戲后這兩個UI已經銷毀了。

之后調用下對應的Resources的相關接口,按理來說圖集貼圖就應該釋放掉了。

Resources.UnloadUnusedAssets()

然而並沒有,用Profiler檢查了,發現被父級的層級Layer里的GraphicRaycaster引用了(比如下圖的英雄界面背景)。

基本上每次進單局都有10mb左右的內存沒釋放掉

問題排查

暴力重建

項目主程給我的建議把這層Layer直接Destroy掉重建,這樣確實能解決問題,但是有可能摧毀的時候上面還有其他UI,導致UI注冊信息還在相關的gameObject卻沒了,訪問UI的時候會拋出NullReference的異常,所以這個方法太簡單暴力了,不好。

查閱源碼

嘗試閱讀了GraphicRaycaster的源碼,發現它內部維護了兩個Graphic的列表

這兩個列表只有在發起一次新的射線的時候才會清空(進單局后選人和加載的Layer子節點下不會有新的UI接受觸摸射線了),於是猜測可能是List一直沒清空導致的圖集無法釋放。

emmmmm,官方還打上了反序列化的標簽,這樣編輯器的Debug模式下也無法查看這兩個List的數據了。

沒辦法,拷貝了整個代碼到一個新類TestGraphicRaycaster,把這兩個標簽替換為SerializeField,然后把相關Layer上的GraphicRaycaster用這個腳本替換了下,然后運行游戲。

果然跟猜測的一樣,是m_RaycastResults這個列表保存的Graphic沒有Clear掉,這樣Graphic無法被GC回收,進而導致Graphic持有的圖集也無法被釋放掉。

找到問題了,那就好解決了。

解決方法

直接清理列表

創建一個新類,把GraphicRaycaster的源碼拷貝到新類當中,然后添加一個清理的接口

public void ClearRaycastResults()
{
     if(m_RaycastResults != null)
     {
        m_RaycastResults.Clear();
     }
}

使用反射清理列表

如果你沒有源碼或者不想修改源碼,那么用反射獲取對應的列表清理也是可以的。

using System.Reflection;

//放在你的工具類里

public static void ClearRaycastResults(GraphicRaycaster gRaycaster)
{
    if(gRaycaster != null)
    {
        var fieldInfo = gRaycaster.GetType().GetField("m_RaycastResults", BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Instance);
        if (fieldInfo != null)
        {
            List<Graphic> list = fieldInfo.GetValue(gRaycaster) as List<Graphic>;
            if(list != null)
            {
               list.Clear();
            }
        }
    }
 }

然后在釋放資源前調用下上述方法清理掉GraphicRaycaster的列表就行了 

參考資料

相關信息可以參考我在UnityAnswer上發布的這個問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM