關於AssetBundle的卸載

關於AssetBundle的卸載

又坐了一天月子,繼續寫文章找狀態。html

本文是關於 卸載AssetBundle 的一些知識點。git

下圖是一個最簡單的從 AssetBundle 加載 Asset實例化 的流程:github

這裏的 Bundle 在加載完 資源A 後就沒用了,咱們能夠經過 AssetBundle.Unload(false) 把它卸掉,只保留住 資源A緩存

若是 資源A 也沒用了,咱們能夠經過 Destroy 接口或者 Resources.UnloadAsset 接口銷燬它。ide

題外話,咱們須要注意一下 Resources.UnloadAsset 的應用場合:函數

This function can only be called on Assets that are stored on disk.this

The referenced asset (assetToUnload) will be unloaded from memory. The object will become invalid and can't be loaded back from disk. Any subsequently loaded Scenes or assets that reference the asset on disk will cause a new instance of the object to be loaded from disk. This new instance will not be connected to the previously unloaded object.spa

UWA 也有相關的回答:3d

Resources.UnloadAsset僅能釋放非GameObject和Component的資源,好比Texture、Mesh等真正的資源。對於由Prefab加載出來的Object或Component,則不能經過該函數來進行釋放。code

好了,回到上圖。

上圖描述的場景過於簡單,實際項目中,資源A 可能依賴 其餘資源,而且 其餘資源 又被打進 不一樣的Bundle 中,以下圖:

這個時候,卸載 AssetBundle 就須要必定的 策略 了。

在介紹 卸載策略 以前,咱們必須先了解清楚 AssetBundle.Unload 這個函數。

關於AssetBundle.Unload

Unity官方文檔對於 AssetBundle.Unload 的描述以下:

public void Unload(bool unloadAllLoadedObjects);
複製代碼

Unloads assets in the bundle.

When unloadAllLoadedObjects is false, compressed file data for assets inside the bundle will be unloaded, but any actual objects already loaded from this bundle will be kept intact. Of course you won't be able to load any more objects from this bundle.

When unloadAllLoadedObjects is true, all objects that were loaded from this bundle will be destroyed as well. If there are GameObjects in your Scene referencing those assets, the references to them will become missing.

  • AssetBundle.Unload(false) 會把 Bundle 卸載,可是已經從 Bundle 里加載出來的 資源 是不會被卸載的。

  • AssetBundle.Unload(true) 不但會卸載 Bundle,也會卸載已經從 Bundle 里加載出來的 全部資源,哪怕這些 資源 還被引用着。

對於用戶來講,若是選擇 AssetBundle.Unload(true),用戶必須確保 Bundle 中已經加載的 資源 是沒有被引用的,不然就會發生 資源丟失

若是選擇 AssetBundle.Unload(false),用戶就要承擔起卸載 已加載資源 的責任,若是處理不當,就可能形成 資源重複,以下圖:

最後,Unity提供了一個 Resources.UnloadUnusedAssets 接口幫助咱們銷燬沒有任何引用的 野資源,不過這個函數會掃描所有對象,開銷較大,通常只在 切場景 時調用。

卸載AssetBundle的策略

瞭解 AssetBundle.Unload 的行爲後,再來看一下咱們採用過的策略。

暗黑血統的策略

最先作 暗黑血統 的時候,咱們卸載 AssetBundle 的策略以下:

  • AssetBundle.Unload(false) 來卸載 Bundle

  • 加載完資源後當即卸載 葉子節點的Bunlde,這裏 葉子節點 表示 沒有被其餘Bundle所依賴的Bundle

  • 對於 非葉子節點的Bundle,卸載邏輯徹底依靠 引用計數

如下圖爲例:

紅框標註的 Bundle 能夠在加載完 資源 後馬上卸載。

咱們看一下包含 資源A和B的Bundle,若是咱們只加載了 A,而後就把 Bundle 卸載了,而後咱們再加載 B,這個時候 Bundle 又要被從新加載,若是我再從這個 Bundle 加載 A,這個時候不是就有 2個A 了?

事實上,由於 第一個A 依然還被咱們的 AssetManager 管理着,上層邏輯不會直接從 Bundle 中去加載 A,而是從 AssetManager的緩存 中去拿,因此上述狀況並不會出現。

咱們再看一下包含 資源C的Bundle,若是咱們加載完 C 後就把 Bundle 卸載了,而後咱們再去加載 G,因爲 G依賴CC所在的Bundle 會再次被加載,同時加載出一個 新的C,這就是真正的 資源重複 了。

此外,由於咱們的 AssetManager 只會管理 直接加載的資源,假設咱們先加載了 GC 作爲 G 的依賴被 間接加載,此時咱們再去直接加載 C 就沒法命中 AssetManager 的緩存了。對於這種狀況,AssetManager 必須作一些額外記錄,稍微有點蛋疼。

最後,考慮一下 引用計數,假設咱們已經銷燬了 G,那麼 G 依賴的全部Bundle引用計數會減 1,假設 包含D的Bundle 以及 包含H的Bundle 引用計數都爲 了,選擇 AssetBundle.Unload(false) 的結果是被 間接加載D和HBundle卸載 後依然存活着,咱們的 AssetManager 並無管理到它們,它們變成了 野資源

這一類 野資源 最終得依靠 Resources.UnloadUnusedAssets 來卸載,通常咱們在 場景切換 時才作這個操做。

當前項目的策略

暗黑血統的策略在線上工做良好,不過由於 AssetManager 只會管理 直接加載 的資源,AssetBundle.Unload(false) 必須配合 Resources.UnloadUnusedAssets 才能完成一次完全的資源清除。

當前項目,AssetManager 依然只管理 直接加載 的資源,不過我選擇了 AssetBundle.Unload(true) 的策略,而且再也不區分 Bundle是不是葉子節點,一切卸載的依據都是 引用計數

正所謂 Bundle在,資源在,Bundle亡,資源亡,:)

如下圖爲例,最左邊一列會標記出每一個 Bundle 的引用計數:

當咱們銷燬 資源A,引用計數變化以下:

當咱們再銷燬 資源B,部分 Bundle 的引用計數變爲0,調用 AssetBundle.Unload(true) 就能把資源及時清理乾淨了,以下圖:

只要引用計數沒問題,理論上 Resources.UnloadUnusedAssets 也就不用了。

總結

當前項目的方案在 資源回收的及時性 上要優於 暗黑血統 的方案,可是 暗黑血統 由於 提早卸載Bundle 的策略,Bundle數量 會更少一點。

其實早期 暗黑血統提早卸載Bundle 的方案上作得更加激進:只有 被共享的Bundle 纔不會被提早卸載,而非我上文所說的 葉子節點,固然,要堵很多bug。

考慮到目前 LZ4 的壓縮策略,以及 AssetBundle.LoadFromFile 的加載方式,多一點 Bundle 的內存佔用不會很高,可是多一點 資源 內存佔用就比較高了。

題外話,下圖是 UWA 關於 LZ4LZMA 的總結,留圖備忘:

本文就到這裏,今年有機會也能夠試試Unity推薦的 Addressable Assets

我的主頁

本文的我的主頁連接:baddogzz.github.io/2020/02/07/…

好了,拜拜。

相關文章
相關標籤/搜索