天天看點

資源架構XAsset閱後總結

        上個月花時間把XAsset這套資源架構的代碼看了一遍且寫了一遍。XAsset官網:https://xasset.github.io/#/compare-plans。我看的是它的7.0體驗版本,是以功能上并不完整,不過盡管如此還是學到了很多的東西。

      XAsset架構的代碼量很少,架構很清晰,我認為作為一個學習的目的來看這個架構是非常不錯的選擇(老實說在這之前我去看Addressable的代碼,是沒能看下去),它也讓我對這一部分知識的了解有了提升,并且在此基礎上我回頭再次看了一遍項目中使用的架構,又有了新的了解。

        在看的同時,我畫了它的UML圖,本想放出來,但是已經找不到扔哪去了。。。另外,我把它與項目中正使用的架構從加載和解除安裝兩個方面進行了仔細的對比,寫了份文章,然而項目代碼是不能随便放的,是以也隻能存在我的有道雲筆記裡。至于熱更部分,由于XAsset我看的隻是一個體驗版本,不僅是個Demo并且沒有經曆過實際項目的打磨,沒什麼好比較的。

         在對比過程中,我對我認為不夠好的一些地方做了修改:

        加載部分

        主要是GC問題,在加載流程中所使用到的new對象的操作,我一律做了池化處理,主要是解決架構本身帶來的GC問題,這個問題在項目中肯定是很嚴重的。

        另外,我增加了直接加載AssetBundle的操作,因為有一些資源是需要直接加載AB統一做處理的,比如shader、配置表。

        解除安裝部分

        原XAsset做了一個簡單的引用計數。當你加載asset的時候,asset會計一個引用,同時它所在的AB以及依賴AB也均計一個引用。當你調用接口release此asset時,asset、AB、依賴AB的引用也都相應減1。而當這些東西引用為0時,會從緩存清單中移除并且AB會調用unload(true)來移除掉。這樣的機制是沒有問題的,問題出在假如業務層沒有正确的去調用release接口,那麼緩存的asset以及對應AB、依賴AB都沒有辦法解除安裝掉,會一直存在于記憶體中。這個機制無疑是有問題的,因為業務層的操作是無法保證的。

        是以針對這個問題,我把它的引用計數改成了兩種形式,一種是單純的使用一個數字去計引用,而另一種則使用weakReference的機制來避免這個問題,首先貼一下代碼:

using System;
using System.Collections.Generic;
using UnityEngine;

namespace Framework.Res.Runtime
{
    public class WeakRef : IReference
    {
        private List<WeakReference> _references;

        public WeakRef()
        {
            _references = new List<WeakReference>();
        }

        public bool Unused
        {
            get { return GetReferenceCount() <= 0; }
        }

        public void Retain(object owner)
        {
            if (owner == null)
            {
                Logger.LogError("owner is null");
                return;
            }

            int count = _references.Count;
            for (int i = 0; i < count; i++)
            {
                if (owner.Equals(_references[i].Target))
                {
                    return;
                }
            }

            WeakReference weakReference = new WeakReference(owner);
            _references.Add(weakReference);
        }

        public void Release(object owner)
        {
            if (owner == null)
            {
                Logger.LogError("owner is null");
                return;
            }

            int count = _references.Count;
            for (int i = 0; i < count; i++)
            {
                if (owner.Equals(_references[i].Target))
                {
                    _references.RemoveAt(i);
                    break;
                }
            }
        }

        public int GetReferenceCount()
        {
            int count = 0;
            for (int j = 0; j < _references.Count; j++)
            {
                if (_references[j].Target != null)
                {
                    if (_references[j].Target.GetType() == typeof(GameObject))
                    {
                        GameObject go = (GameObject)_references[j].Target;
                        if (go != null)
                        {
                            count++;
                        }
                        else
                        {
                            //Target不為null,go為null,特殊處理
                            _references.RemoveAt(j);
                            j--;
                        }
                    }
                    else
                    {
                        count++;
                    }
                }
            }
            return count;
        }

        public void Reset() 
        {
            _references.Clear();
        }
    }
}
           

         具體怎麼使用呢?一般我們加載完資源以後,會提供一個接口供業務層擷取這個資源,這時候我要求業務層傳入一個持有者。比如說一個UI元件用到一張圖檔,那麼你想得到這個圖檔的資源,就把這個元件對象傳進來:

public Object GetAsset(object owner)
        {
            if (owner == null) return null;

            reference.Retain(owner);
            return asset;
        }
           

        而此同時,在内部用weakReference将這個引用記下來,并且在GetReferenceCount時通過WeakReference.Target便可知道這個UI元件還是否存在,若不存在,則也代表你不再需要這個資源了,便可不必再計這個引用。

        這樣一來,業務層便不再需要主動調用Release()接口去釋放自己的引用,隻需要銷毀你這個持有者便可,最壞的情況,也會在切換場景時自動被銷毀掉。不過使用這種方法,需要再update中定時去檢測每個資源的引用情況,不過相比較于它原生的那種必須正确使用的方式,這樣保證了那些不再被使用的資源可以正确的被釋放掉。

        另外,上面說到了直接加載AB的情況,針對這種情況,一般在拿到AB後會自己通過AB去加載AB内的資源然後緩存起來,是以我在接口回調之後添加了自動解除安裝AB的操作,讓上層不必考慮解除安裝的問題。同時,這時候在AB沒有引用時也不能再使用unload(true),因為這樣會一并解除安裝掉上層緩存的東西,是以需要改成unload(false)。

繼續閱讀