关于AssetBundle的卸载

关于AssetBundle的卸载

又坐了一天月子,继续写文章找状态。

本文是关于 卸载AssetBundle 的一些知识点。

下图是一个最简单的从 AssetBundle 加载 Asset实例化 的流程:

image

这里的 Bundle 在加载完 资源A 后就没用了,我们可以通过 AssetBundle.Unload(false) 把它卸掉,只保留住 资源A

如果 资源A 也没用了,我们可以通过 Destroy 接口或者 Resources.UnloadAsset 接口销毁它。

题外话,我们需要注意一下 Resources.UnloadAsset 的应用场合:

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

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.

UWA 也有相关的回答:

Resources.UnloadAsset仅能释放非GameObject和Component的资源,比如Texture、Mesh等真正的资源。对于由Prefab加载出来的Object或Component,则不能通过该函数来进行释放。

好了,回到上图。

上图描述的场景过于简单,实际项目中,资源A 可能依赖 其他资源,并且 其他资源 又被打进 不同的Bundle 中,如下图:

image

这个时候,卸载 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),用户就要承担起卸载 已加载资源 的责任,如果处理不当,就可能造成 资源重复,如下图:

image

最后,Unity提供了一个 Resources.UnloadUnusedAssets 接口帮助我们销毁没有任何引用的 野资源,不过这个函数会扫描全部对象,开销较大,一般只在 切场景 时调用。

卸载AssetBundle的策略

了解 AssetBundle.Unload 的行为后,再来看一下我们采用过的策略。

暗黑血统的策略

最早做 暗黑血统 的时候,我们卸载 AssetBundle 的策略如下:

  • AssetBundle.Unload(false) 来卸载 Bundle

  • 加载完资源后立即卸载 叶子节点的Bunlde,这里 叶子节点 表示 没有被其他Bundle所依赖的Bundle

  • 对于 非叶子节点的Bundle,卸载逻辑完全依靠 引用计数

以下图为例:

image

红框标注的 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 的引用计数:

image

当我们销毁 资源A,引用计数变化如下:

image

当我们再销毁 资源B,部分 Bundle 的引用计数变为0,调用 AssetBundle.Unload(true) 就能把资源及时清理干净了,如下图:

image

只要引用计数没问题,理论上 Resources.UnloadUnusedAssets 也就不用了。

总结

当前项目的方案在 资源回收的及时性 上要优于 暗黑血统 的方案,但是 暗黑血统 因为 提前卸载Bundle 的策略,Bundle数量 会更少一点。

其实早期 暗黑血统提前卸载Bundle 的方案上做得更加激进:只有 被共享的Bundle 才不会被提前卸载,而非我上文所说的 叶子节点,当然,要堵不少bug。

考虑到目前 LZ4 的压缩策略,以及 AssetBundle.LoadFromFile 的加载方式,多一点 Bundle 的内存占用不会很高,但是多一点 资源 内存占用就比较高了。

题外话,下图是 UWA 关于 LZ4LZMA 的总结,留图备忘:

image

本文就到这里,今年有机会也可以试试Unity推荐的 Addressable Assets

个人主页

本文的个人主页链接:https://baddogzz.github.io/2020/02/07/Unload-Resources/

好了,拜拜。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351