一、经典的对称造型,用多少释放多少。
这是各阶段的内存和其他数据变化
说明:
- 初始状态
- AssetBundle.Load文件后:内存多了文件镜像,Memory+4.4MB,Total Object和Assets+1【AssetBundle也是object】
- 载入Texture后: Memory+4MB,因为多了Texture Asset占用的内存,Total Objects和Assets+1
- 载入Prefab后:内存无明显变化,因为最占内存的Texture已经加载,Materials+1是因为多了Prefab的材质,Total Objects和Assets+6,因为 Perfab 包含很多 Components
- 实例化Prefab以后:显存(Texture Memory)+4MB、GameObject、Total Objects in Scene++,都是因为实例化了一个可视的对象
- 下面是开始对称的操作:销毁
- 销毁实例后:Texture Memory-4MB,GameObject、Total Objects in Scene--
- 卸载AssetBundle文件后:AssetBundle文件镜像占用的内存被释放Memory-4.3MB(??泄露了?),相应的Assets和Total Objects Count-1
- 直接Resources.UnloadUnusedAssets:没有任何变化,因为所有Assets引用并没有清空
- 把Prefab引用变量设为null以后,再Resources.UnloadUnusedAssets:整个Prefab除了Texture外都没有任何引用了,所以被UnloadUnusedAssets销毁,Assets和Total Objects Count -6,Materials-1
- 再把Texture的引用变量设为null,再Resources.UnloadUnusedAssets :之后也被UnloadUnusedAssets销毁,内存被释放Texture Memory-4MB,assets和Total Objects Count -1,基本还原到初始状态
总结:
ab加载完(AssetBundle.Load),内存++【多了文件镜像】
实例化预制(Instance),显存++【多了可视对象】
Texture加载以后是到内存,显示的时候才进入显存的Texture Memory。
所有的东西基础都是Object
Load的是Asset,Instantiate的是GameObject和Object in Scene
Load的Asset要Unload才能卸载干净,new的或者Instantiate的object可以直接Destroy
一、ab加载与释放
- AssetBundle.CreateFromFile运行时加载:只是个AssetBundle内存镜像数据块
- AssetBundle.Load(同Resources.Load) 这才会从AssetBundle的内存镜像里读取并创建一个Asset对象,创建Asset对象同时也会分配相应内存用于存放(反序列化)
- AssetBundle的释放:
AssetBundle.Unload( flase)是释放AssetBundle文件的内存镜像, 不包含Load创建的Asset内存对象。
AssetBundle.Unload(true)是释放那个AssetBundle文件内存镜像和并销毁所有用Load创建的Asset内存对象。这个蛮重要的,不注意的话会造成材质引用丢失。
二、预制实例化
一个Prefab从assetBundle里Load出来,里面可能包括:Gameobject、transform、mesh、texture、material、shader、script和各种其他Assets。
你实例化一个Prefab,其实是一个对Assets进行Clone(复制)+引用结合的过程
- 其他mesh / texture / material / shader 等,这其中些是纯引用的关系的,包括:Texture和TerrainData
- 还有引用和复制同时存在的,包括:Mesh/material /PhysicMaterial。
- 引用的Asset对象不会被复制,只是一个简单的指针指向已经Load的Asset对象。这种含糊的引用加克隆的混合, 大概是搞糊涂大多数人的主要原因。
总结一下各种释放
- Destroy: 主要用于销毁克隆对象,也可以用于场景内的静态物体,不会自动释放该对象的所有引用。虽然也可以用于Asset,但是概念不一样要小心,如果用于销毁从文 件加载的Asset对象会销毁相应的资源文件!但是如果销毁的Asset是Copy的或者用脚本动态生成的,只会销毁内存对象。
- AssetBundle.Unload(false):释放AssetBundle文件内存镜像
- AssetBundle.Unload(true):释放AssetBundle文件内存镜像同时销毁所有已经Load的Assets内存对象
- Reources.UnloadAsset(Object):显式的释放已加载的Asset对象,只能卸载磁盘文件加载的Asset对象
- Resources.UnloadUnusedAssets():用于释放所有没有引用的Asset对象
- GC.Collect():强制垃圾收集器立即释放内存 Unity的GC功能不算好,没把握的时候就强制调用一下
举两个例子帮助理解
例子1:
一个常见的错误:
你从某个AssetBundle里Load了一个prefab并克隆:
obj = Instaniate(AssetBundle1.Load('MyPrefab”);
这个prefab比如是个npc。然后你不需要他的时候你用了:Destroy(obj);你以为就释放干净了。其实这时候只是释放了Clone对象,通过Load加载的所有引用、非引用Assets对象全都静静静的躺在内存里。
这种情况应该在Destroy以后用:AssetBundle1.Unload(true),彻底释放干净。
- 如果这个AssetBundle1是要反复读取的。不方便Unload,那可以在Destroy以后用:Resources.UnloadUnusedAssets(); 把所有和这个npc有关的Asset都销毁。
- 当然如果这个NPC也是要频繁创建销毁的,那就应该让那些Assets呆在内存里以加速游戏体验。
由此可以解释另一个之前有人提过的话题:为什么第一次Instaniate一个Prefab的时候都会卡一下?
因为在你第一次Instaniate之前,相应的Asset对象还没有被创建,要加载系统内置的 AssetBundle并创建Assets。第一次以后你虽然Destroy了,但Prefab的Assets对象都还在内存里,所以就很快了。
例子2:
从磁盘读取一个1.unity3d文件到内存并建立一个AssetBundle1对象
AssetBundle AssetBundle1 = AssetBundle.CreateFromFile("1.unity3d");
从AssetBundle1里读取并创建一个Texture Asset,把obj1的主贴图指向它
obj1.renderer.material.mainTexture = AssetBundle1.Load("wall") as Texture;
把obj2的主贴图也指向同一个Texture Asset
obj2.renderer.material.mainTexture =obj1.renderer.material.mainTexture;
Texture是引用对象,永远不会有自动复制的情况出现(除非你真需要,用代码自己实现copy),只会是创建和添加引用
- 如果继续:AssetBundle1.Unload(true) 那obj1和obj2都变成黑的了,因为指向的Texture Asset没了
- 如果:AssetBundle1.Unload(false) 那obj1和obj2不变,只是AssetBundle1的内存镜像释放了
- 继续:Destroy(obj1); //obj1被释放,但并不会释放刚才Load的Texture
- 如果这时候:Resources.UnloadUnusedAssets();//不会有任何内存释放 因为Texture asset还被obj2引用着
- 如果Destroy(obj2);//obj2被释放,但也不会释放刚才Load的Texture
- 继续Resources.UnloadUnusedAssets();//这时候刚才load的Texture Asset释放了,因为没有任何引用了
- 最后CG.Collect();强制立即释放内存
由此可以引申出论坛里另一个被提了几次的问题:
如何加载一堆大图片轮流显示又不爆掉?
不考虑AssetBundle,直接用www读图片文件的话等于是直接创建了一个Texture Asset 假设文件保存在一个List里
TLlist<string> fileList;
int n=0;
IEnumerator OnClick()
{
WWW image = new www(fileList[n++]);
yield return image;
Texture tex = obj.mainTexture;
obj.mainTexture = image.texture;
n = (n>=fileList.Length-1)?0:n;
Resources.UnloadAsset(tex);
}
这样卸载比较快
问题1:先说一个遇到的坑,当大量(几百个)AssetBundle加载的时候(可能是WWW加载的时候,也可能是AssetBundle.LoadAsset的时候),Android手机上会闪退。
看崩溃log是多线程文件访问的时候崩溃了。
解决方法是减少同时加载的AB数量(这个是纯逻辑控制),使用的是AssetBundle.LoadFromFile接口。
【Unity游戏开发】AB学习(三)加载和实例化的内存变化 - 知乎