当前位置:   article > 正文

unity资源的计数型AssetBundle加载管理_unity资源计数

unity资源计数

unity资源的计数型AssetBundle加载管理


内存管理有我们动态申请的程序运行或者计算使用的内存,还有一部分就是动态加载进来的资源的内存。

那这篇说的是关于资源内存管理的。

说的好像很高端,其实是个很简单的东西
在MVC的结构下,资源内存管理和AB加载是一个独立的模块
资源内存管理引用加载器,形成一个单独的模块我把它叫做Loader(加载器)


首先第一点,为什么要做资源内存管理

很明显,不用的资源可以被正确卸载。不至于我们的项目在运行的时候闪退崩溃。

目前最好用,最普遍的的一套内存管理的概念就是计数引用。其实和unity自身的内存管理或者C#,java的垃圾回收机制类似。
当一个object没有引用的时候就去释放它。

那Unity自带的 Resources.UnloadUnusedAssets 之类的方法,去检查资源是否还有引用的,没有引用就卸载。这种方法存在很大缺点,就是会遍历所有资源倒是顿卡。所以我们需要一套自己的资源管理方法。
相关的资料网上有很多,不需要重复解释了。

那做计数管理的好处就是可以知道什么该卸载资源了,而不是重复使用同一个AB之后不知道什么时候该卸载


那接下来直接说内存内存管理的一个原理
一个Unity游戏的资源加载流程一般分为两种 Sync 和 Async,也就是同步和异步的加载,
不管在现有的unity的哪个里 加载就是 AssetBundle.Load 或者 AssetBundle.LoadAsync只是写法不同。
那我使用的加载都是Async的,也就是异步,可有有效的避免 Sync的IO阻塞导致的卡顿。

Load的流程一般来说

界面请求资源->加载->得到AB->提取资源->使用资源

LoadAsync的流程是

界面请求资源->等待加载器回调
加载器加载资源->提取资源->回调
界面收到回调->使用资源

使用计数管理很重要的一点是需要外部的逻辑 加载卸载对齐
加载必须有卸载,创建必须有销毁。在确保外部逻辑合理的情况下,计数才有意义。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 单个Asset加载的状态;
/// 因为使用异步加载,加载是有个过程的.
/// 在过程中可能需要外部获取的信息或者其他操作;
/// </summary>
public enum AssetState : byte
{
	None,

	Loading,
	Finish,
	//...
}

/// <summary>
/// 一个资源的信息;
/// </summary>
public class AssetPack
{
	public AssetPack(string strName, string strLoadPath)
	{
		m_strLoadPath = strLoadPath;
		m_strName = strName;
	}

	//一些必须资料;
	private string m_strName = null; // 资源名;
	private string m_strLoadPath = null; // 加载路径;
	public System.Action<string> m_CallBack = null; // 回调;

	//运行时内容;
	public int m_nRef = 0;          // 资源的引用计数;
	public AssetState m_AssetState = AssetState.None;   // 加载状态;

	//加载结果内容
	public AssetBundle m_AssetBundle = null;    // 资源本体;

	public IEnumerator LoadAsync()
	{
		//TODO 资源的具体加载逻辑;
		m_AssetState = AssetState.Loading;
		// 开始加载
		yield return null;
		// 等待加载完成
		yield return null;
		// 加载完成
		// 回调说明完成;
		m_AssetState = AssetState.Finish;
		if (m_CallBack != null)
		{
			m_CallBack(m_strName);
		}
	}
}

/// <summary>
/// 给外部使用的加载器;
/// Sync 和 Async 在这里是对于外部逻辑来说的同步或者异步.
/// 不是IO方面的Async.
/// 因为外部有时候需要利用Coroutine做一些流程化的加载步骤. 需要Sync
/// 或者是界面的图片加载不在乎时序,加载即显示的内容.需要Async
/// </summary>
public class AssetLoader
{
	private Dictionary<string, AssetPack> m_dicAllAssets = new Dictionary<string, AssetPack>();

	public IEnumerator LoadSync(string strName, string strPath/*, 其他需要的参数*/)
	{
		/*因为是Sync的,所以在这套流程里应该是不需要回调的参数
		 * 因为Coroutine之后一定会有结果,加载成功 或者 资源不存在.
		 * 回调本来就是用来通知加载完成的,所以这里不需要回调.
		 */

		// 直接调用下面的对外接口.
		// 因为这里是概念和逻辑上的Async和Sync区分,不是IO,所以组合逻辑;
		LoadAsync(strName, strPath, null/*, 其他需要的参数*/);

		while (!ChechLoadFinish(strName))
		{
			yield return null;
		}
	}

	/// <summary>
	/// 加载逻辑
	/// </summary>
	public void LoadAsync(string strName, string strPath, System.Action<string> cb/*, 其他需要的参数*/)
	{
		/*加载本身是一套线性流程,没什么问题,按照官方Demo就可以了.
		 * 那我们要做的是避免重复加载资源.
		 * 所以:
		 */

		if (m_dicAllAssets.ContainsKey(strName))
		{
			// 如果这个资源已经申请下载过了,或者还没有被卸载,那么m_dicAllAssets一定有这个key存在.
			AssetPack ap = m_dicAllAssets[strName];
			ap.m_nRef++; // 因为这个加载的请求,所以计数+1;
			ap.m_CallBack += cb;

			// 这里判断资源是否已经加载完成;
			if (ap.m_AssetState == AssetState.Finish)
			{
				//如果资源加载完成,直接回调;
				/*因为在申请加载这个资源的时候有几种情况
				 * 1.这个资源已经被加载过,并且在使用
				 * 2.这个资源不存在与内存,也就是说 没有加载过,或者已经被卸载
				 * 3.这个资源正在加载中.
				 * 
				 * 所以在1的情况下,我们不需要重新加载这个资源,直接可以去到引用,外部直接获取就可以.
				 */
				cb(strName);
			}
		}
		else
		{
			//如果Dictionary没有这个key,那么:
			AssetPack ap = new AssetPack("", "");
			ap.m_CallBack = cb;
			ap.m_nRef = 1;  // 添加一个计数;

			/*启动Coroutine*/ap.LoadAsync();  // 这里我使用的是 管理空脚本的GameObject来启停Coroutine,这部分可以再说.
			/*在这个启动Coroutine加载完成后,由内部调用CallBack,在加载过程中,在上面的if里会增加callback,会一起调用。
			 * 这样就满足了 上面的if中说到的3种情况.
			 */

			m_dicAllAssets.Add(strName, ap);
		}
	}
	
	/// <summary>
	/// 对外卸载用的接口;
	/// </summary>
	public void Unload(string strName, System.Action<string> cb)
	{
		// 卸载的逻辑就不具体写了
		/*判断是否存在
		 * 如果存在扣除计数, 减掉cb
		 * 如果计数为0, Dictionary.Remove 
		 */
		/*需要注意的是 在确定Remove的时候要停止Coroutine,因为可能UnLoad的时候这个资源正在加载.比如开了界面,马上关闭*/
	}

	/// <summary>
	/// 检查加载是否完成;
	/// </summary>
	public bool ChechLoadFinish(string strName)
	{
		if (m_dicAllAssets.ContainsKey(strName))
		{
			AssetPack ap = m_dicAllAssets[strName];
			// 这里检查
			return ap.m_AssetState == AssetState.Finish; 
		}
		return false;
	}

	public AssetBundle GetAssetBundle(string strName)
	{
		if (m_dicAllAssets.ContainsKey(strName))
		{
			// 获取内容return;
			return m_dicAllAssets[strName].m_AssetBundle;
		}
		return null;
	}
}

public class DemoAsync : MonoBehaviour
{
	[SerializeField] Image image = null;

	private AssetLoader al = new AssetLoader();	// AssetLoader是作为单例存在,还是作为Logic的成员存在,其实是无所谓的.看怎么合理怎么来;

	void Start ()
	{
		al.LoadAsync("img_000.ab","路径", OnCallBackLoadFinish);
	}

	private void OnCallBackLoadFinish(string strName)
	{
		//这个函数的参数返回strName可以判断加载是否完成.批量预加载某些资源的时候会有用;

		// 这里直接名字获取就可以;
		AssetBundle ab = al.GetAssetBundle("img_000.ab");
		//之后可以获取资源进行操作了
		// 要说明一下,这里只是简单的写了,ab应该由AssetPack确定资源类型后,在AssetPack加载完后,直接提取内容,外部直接使用具体资源;
		//image.sprite = ***;
	}

	void OnDestory()
	{
		al.Unload("img_000.ab", OnCallBackLoadFinish);
	}
}

public class DemoSync : MonoBehaviour
{
	private AssetLoader al = new AssetLoader();

	public void LoadBattleScene()
	{
		StartCoroutine("LoadBattleSceneAsync");
	}

	private int m_nProgress = 0;	// 切换场景加载进度;

	private IEnumerator LoadBattleSceneAsync()
	{
		m_nProgress = 0;

		yield return al.LoadSync("battlescene.ab", "路径");
		//SceneManage.LoadScene

		m_nProgress = 30;

		yield return al.LoadSync("player.ab", "路径");
		//PlayerLogic.Create

		m_nProgress = 60;

		yield return al.LoadSync("music.ab", "路径");
		//music.init

		m_nProgress = 100;
		//等等类似这样的流程逻辑,可以用同步方法
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234

程序学无止尽。
欢迎大家沟通,有啥不明确的,或者不对的,也可以和我私聊
我的QQ 334524067 神一般的狄狄

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/341588
推荐阅读
相关标签
  

闽ICP备14008679号