资源篇 资源加载

写作动机

鉴于分析项目资源加载流程源码之后,就想自己总结优化出一套更加好的框架来,目的在于可扩展和调试方便。项目中遇到太多的坑了,一个是加载本身有问题导致加载失败,另外一个是引用计数有问题导致卸载不掉,还有一些问题是不知道什么地方把正在使用的资源卸载了,导致野指针,而这些问题查找起来并不轻松;使用者并不清楚,也不需要清楚加载的具体逻辑,所以会导致频繁的加载、卸载和取消加载混杂其中,一个不注意就坑爹了,错误往往随机暴露出来。项目中为了解决这些问题,往往是缝缝补补,最终没法看了,你可能会问为啥不重构?重构是不可能的这辈子都不可能,好歹也经历过上线项目,你一重构指不定有啥问题。

本篇重点在于加载架构上:加载流程,引用计数规则,取消加载注意点等操作上面,具体的实现细节不会太多提及,以下是一些很棒的基础学习链接(一定记得看啊):
https://blog.csdn.net/qq_35433081/article/details/80671007
https://gameinstitute.qq.com/community/detail/110986

手分=========

资源加载总流程图

在这里插入图片描述
图说明

  1. 图中实线箭头代表同帧执行,虚线箭头代表不同帧执行;
  2. +1、-1代表当前asset或bundle的增减引用数,可以看出,每次加载完成之后当前asset或bundle的引用计数都会增加1,这个增加1会在释放操作的时候减去,从而回收资源;
  3. CA、CB、CB2代表加载成功后的回调,说明依赖加载完成,将要加载自身了;
  4. 自身加载分为开始加载和结束加载两个状态,但是unity并没有提供取消加载的操作,所以我们让这两个状态合并,不可分割,这样做的目的是防止取消加载时的一些问题,下面会详细说取消加载的一些问题;
流程分析

看图分析1 加载bundle tex
在这里插入图片描述

  1. 红色部分是状态流程:EnterLoadWait(等待加载)–>EnterLoading(依赖加载和自身加载)–>EnterWaitCall(加载回调),这里有一个
    问题,图中并没有依赖加载的步骤,这是因为我把bundle的依赖加载和加载自身合并了,但是并不影响分析问题。
  2. 绿色圈圈里面的两个数是:当前引用值和当前增减值。看图发现加载完成之后最终是1,因为回调的时候会+1,使用者使用完成这个资源后必须释放这个引用。

看图分析2 加载资源tex1;资源tex1在bundle tex中
在这里插入图片描述

  1. 红色是bundle加载,紫色是asset加载;可以看出asset的加载是在bundle加载完成之后才开始加载的。
  2. 引用计数最终都是1,tex1为1的原因是使用者持有1个引用,tex为1的原因是被tex1持有。

现在将上述过程整理一下:
Bundle的加载状态分为:等待加载、依赖加载、自身加载、加载回调
Asset的加载状态分为:等待加载、依赖加载,自身加载、加载回调
看过一些加载的方案,最后还是觉得使用状态的形式是一种较优的形式,扩展起来很方便。下面看一下状态的具体含义:
1. 等待加载状态:目的是控制当前帧的加载上限,出于优化的目的。比如你目前已经加载了8个资源达到加载上限,那么后面的加载将进入等待加载状态,开始加载后就进入依赖加载状态
2. 依赖加载状态: 等待加载依赖的bundle加载完成之后才会去加载自身,所有依赖加载完成之后进入自身加载状态
3. 自身加载状态:每帧检测的主要原因就在这里,每帧检测asset或bundle是否加载完成,如果加载完成,说明当前的加载和依赖加载全部完成,进入加载回调状态
4. 加载回调状态:目的有两个:一是回调并增加引用计数(原因是将asset或者bundle返回给了调用者所以引用计数加1),二是如果asset或bundle已经加载,使用者调用加载接口回调不要同帧执行,不然就是同步加载了,这里在Update中检测状态的话就会晚一帧触发回调给使用者取消加载的机会。

取消加载
  1. 第一个图中的虚线都是可以操作的地方,也就是说图中的虚线任何一处都可以取消。
  2. 主要分两个部分的取消,一是加载依赖的过程中被取消,二是加载自己的过程中被取消了。
  3. 加载依赖被取消,加载依赖的回调就会被删除,从而导致CB2或者CB过程失效,整个加载过程就中断了。
  4. 自身加载时被取消,由于Unity没有提供资源加载中的取消方法,所以我们只能先加载然后在取消。
  5. 由于加载自己和加载依赖是同时进行的加载,然后在Update中检测是否加载完成,那么取消的时候不能单单取消自己,还需要取消依赖加载;比如像下面的加载和依赖关系:
    在这里插入图片描述
    如果只是取消A过程,那么后续的bundle依旧加载完成,然后卸载,这样太过于浪费。
    所以我们要将A、B、C、D过程全部取消。用同一个请求Id标记ABCD四个过程,取消的时候,查找这个Id的所有请求,全部取消即可。
    取消加载时不同状态下的操作
    等待加载状态下取消,删除回调(这时候还没有真的开始加载);
    依赖加载状态下取消,删除回调,取消依赖bundle的加载;
    自身加载状态下取消,删除回调,等待加载完成然后释放;
    加载回调状态下取消,删除回调;

框架难点

  1. 状态的确定
    为什么要分等待加载、依赖加载、自身加载、加载回调这4个状态,我这样分的原因是我在使用的过程中很容易控制和调试。一开始并不是这4个状态,后期慢慢觉得这4个状态很合适而已,当然你可以根据自己的项目要划分状态,并且状态的扩展非常容易,而且调试起来也很方便,直接看asset或者bundle加载到了哪一步就知道问题了。
  2. 引用计数规则
    非常容易计算错,而且在使用者不规范使用的情况下,比如ta就是要加载取消再加载(当然ta自己不知道,这是最终逻辑造成的结果),引用计数根本就没有办法查,但是当过程状态化了之后,你就很容易知道,哪一步对引用造成了影响,引用的增加只会在自身加载成功和回调处产生,引用的减少只会在回收和使用者释放处产生,其中自身加载成功和回收是一个相反的过程。

还有一些细节

  1. 资源加载之后:1 直接引用;2 实例化。这里不管是引用还是实例化,都会增加asset的引用计数(因为回调会导致引用加1),当释放的时候减少引用,Update中检测引用为零时卸载asset,同时减少bundle的引用,bundle引用为零时卸载。
  2. 实例化的GameObject通过GameObejct.Destory卸载,当然资源GameObject我们只能使用assetbundle.unload(true)或者Resources.UnloadUnusedAsset()去卸载。
  3. 项目中使用了三个缓冲池:Bundle缓冲池、Asset缓冲池、GameObject缓冲池。
  4. GameObject实例化:资源加载完成之后有些是需要实例化才能使用的,比如prefab。我们建立一套GameObject的管理,通过InstanceId去管理GameObejct,可以避免出现GameObject被删除了但是引用还在的野指针(这种问题非常难查,所以应尽早避免)。
  5. 对于经常使用的Asset或者GameObject,建立隐藏节点引用之,只要有引用就不会卸载,达到常驻内存的目的,不需要做很多特殊判断,强行写法等等。

这里是项目地址(Unity 5.3.6p7):
https://github.com/xiaoyanxiansheng/AssetManagerFrame
基础学习链接(再贴一次):
https://blog.csdn.net/qq_35433081/article/details/80671007
https://gameinstitute.qq.com/community/detail/110986

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值