前言
最近進行項目性能優化的時候發現的問題。
問題
從大廳進到單局的過程中,會經過選擇英雄和加載兩個流程,這兩個流程對應的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上釋出的這個問題