又坐了一天月子,继续写文章找状态。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 这个函数。
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.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依赖C,C所在的Bundle 会再次被加载,同时加载出一个 新的C,这就是真正的 资源重复 了。
此外,由于咱们的 AssetManager 只会管理 直接加载的资源,假设咱们先加载了 G,C 作为 G 的依赖被 间接加载,此时咱们再去直接加载 C 就没法命中 AssetManager 的缓存了。对于这种状况,AssetManager 必须作一些额外记录,稍微有点蛋疼。
最后,考虑一下 引用计数,假设咱们已经销毁了 G,那么 G 依赖的全部Bundle引用计数会减 1,假设 包含D的Bundle 以及 包含H的Bundle 引用计数都为 0 了,选择 AssetBundle.Unload(false) 的结果是被 间接加载 的 D和H 在 Bundle卸载 后依然存活着,咱们的 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 关于 LZ4 和 LZMA 的总结,留图备忘:
本文就到这里,今年有机会也能够试试Unity推荐的 Addressable Assets。
本文的我的主页连接:baddogzz.github.io/2020/02/07/…。
好了,拜拜。