Laya LoaderManager小記


LoaderManager 類用於用於批量加載資源。此類是單例,不要手動實例化此類,請通過Laya.loader訪問。全部隊列加載完成,會派發 Event.COMPLETE 事件;如果隊列中任意一個加載失敗,會派發 Event.ERROR 事件,事件回調參數值為加載出錯的資源地址。

LoaderManager 類提供了以下幾種功能: 多線程:默認5個加載線程,可以通過maxLoader屬性修改線程數量; 多優先級:有0-4共5個優先級,優先級高的優先加載。0最高,4最低; 重復過濾:自動過濾重復加載(不會有多個相同地址的資源同時加載)以及復用緩存資源,防止重復加載; 錯誤重試:資源加載失敗后,會重試加載(以最低優先級插入加載隊列),retryNum設定加載失敗后重試次數,retryDelay設定加載重試的時間間隔。

LoaderManager是Laya加載資源的統一入口,管理laya的加載器(Laya.Loader),負責控制加載優先級,加載線程維護與上限上線控制,加載失敗后重試等操作。在laya中使用的Laya.loader是LoaderManager的實例,而不是Laya.Loader的實例。

源碼閱讀

屬性簡介
		/**@private */
		private static var _resMap:Object = {};				//@資源map,url做key,resInfo為value, 用於檢測資源是否已經在隊列中,防止重復加載。
		/**@private */
		public static var createMap:Object = {atlas: [null, Loader.ATLAS]};   //@用於檢測create方法的類型。
		
		/** 加載出錯后的重試次數,默認重試一次*/
		public var retryNum:int = 1;
		/** 延遲時間多久再進行錯誤重試,默認立即重試*/
		public var retryDelay:int = 0;
		/** 最大下載線程,默認為5個*/
		public var maxLoader:int = 5;
		
		/**@private */
		private var _loaders:Array = [];     //@緩存的loader列表
		/**@private */
		private var _loaderCount:int = 0;	 //@正在使用的loader數量、加載線程數量
		/**@private */
		private var _resInfos:Array = [];	 //@加載資源列表,存放所有需要加載的資源,按優先級和隊列順序排列
		
		/**@private */
		private var _infoPool:Array = [];	 //@加載數據對象的緩存池
		/**@private */
		private var _maxPriority:int = 5;	 //@優先級數量  0-4
		/**@private */
		private var _failRes:Object = {};	 //@失敗資源的加載次數,用於失敗后重試計數
初始化
  • LoaderManager在Laya.init時創建,賦值給Laya.loader。
  • 按照優先級,創建5(_maxPriority)個隊列,用於存放需要加載的資源數據。隊列的索引就是加載的優先級,0-4,數字越小,優先級越高。加載時,按照優先級和隊列順序依次加載。
		/**
		 * <p>創建一個新的 <code>LoaderManager</code> 實例。</p>
		 * <p><b>注意:</b>請使用Laya.loader加載資源,這是一個單例,不要手動實例化此類,否則會導致不可預料的問題。</p>
		 */
		public function LoaderManager() {
			for (var i:int = 0; i < this._maxPriority; i++) this._resInfos[i] = [];is._resInfos[i] = [];
		}
單個資源加載流程
  • 判斷資源是否存在,如果存在則直接調用progress和complete。加載的資源存放在Laya.Loader中,不在LoaderManager中。
  • 判斷資源是否在加載隊列中:
    • 如果存在,則監聽COMPLETE和PROFRESS事件。注:添加時不能設置offBefore,防止后續加載的資源的回調覆蓋之前的加載完成事件。
    • 如果不存在,則創建ResInfo,將info放到對應優先級隊列的末尾,監聽COMPLETE事件。調用next方法,嘗試加載。
  • 檢測當前正在使用的loader的數量,如果數量不足maxLoader個數,則按優先級查找隊列,再取隊列的第一個數據去加載。如果當前正在使用的loader的count為0,則觸發Laya.loader(LoaderManager)的COMPLETE事件。
  • 加載是使用Laya.Loader執行加載邏輯。
  • 加載完成后(或者失敗),回收當前loader,更新loader數量,如果加載失敗,則重新放入加載隊列,並且優先級設置為最低。如果失敗多次,則出發Error事件,將url傳遞出去。加載成功或者失敗都會出發COMPLETE事件,調用complete方法。清理和回收相關數據。
  • 嘗試調用next,嘗試選擇下一個資源加載。
		/**
		 * <p>加載資源。資源加載錯誤時,本對象會派發 Event.ERROR 事件,事件回調參數值為加載出錯的資源地址。</p>
		 * <p>因為返回值為 LoaderManager 對象本身,所以可以使用如下語法:Laya.loader.load(...).load(...);</p>
		 * @param	url			要加載的單個資源地址或資源信息數組。比如:簡單數組:["a.png","b.png"];復雜數組[{url:"a.png",type:Loader.IMAGE,size:100,priority:1},{url:"b.json",type:Loader.JSON,size:50,priority:1}]。
		 * @param	complete	加載結束回調。根據url類型不同分為2種情況:1. url為String類型,也就是單個資源地址,如果加載成功,則回調參數值為加載完成的資源,否則為null;2. url為數組類型,指定了一組要加載的資源,如果全部加載成功,則回調參數值為true,否則為false。
		 * @param	progress	加載進度回調。回調參數值為當前資源的加載進度信息(0-1)。
		 * @param	type		資源類型。比如:Loader.IMAGE。
		 * @param	priority	(default = 1)加載的優先級,優先級高的優先加載。有0-4共5個優先級,0最高,4最低。
		 * @param	cache		是否緩存加載結果。
		 * @param	group		分組,方便對資源進行管理。
		 * @param	ignoreCache	是否忽略緩存,強制重新加載。
		 * @return 此 LoaderManager 對象本身。
		 */
		public function load(url:*, complete:Handler = null, progress:Handler = null, type:String = null, priority:int = 1, cache:Boolean = true, group:String = null, ignoreCache:Boolean = false):LoaderManager {
			if (url is Array) return _loadAssets(url as Array, complete, progress, type, priority, cache, group);
			var content:* = Loader.getRes(url);
			if (content != null) {
				//增加延遲回掉
				Laya.timer.frameOnce(1, null, function():void {
					progress && progress.runWith(1);
					complete && complete.runWith(content);
					//判斷是否全部加載,如果是則拋出complete事件
					_loaderCount || event(Event.COMPLETE);
				});
			} else {
				var info:ResInfo = _resMap[url];
				if (!info) {
					info = _infoPool.length ? _infoPool.pop() : new ResInfo();
					info.url = url;
					info.type = type;
					info.cache = cache;
					info.group = group;
					info.ignoreCache = ignoreCache;
					//@收到ResInfo的COMPLETE事件則執行complete方法
					complete && info.on(Event.COMPLETE, complete.caller, complete.method, complete.args);
					progress && info.on(Event.PROGRESS, progress.caller, progress.method, progress.args);
					_resMap[url] = info;
					//@選擇對應優先級隊列
					priority = priority < this._maxPriority ? priority : this._maxPriority - 1;
					this._resInfos[priority].push(info);
					_next();
				} else {
					//@如果已經在加載隊列,則只注冊事件,不創建加載數據。
					//@注:這里offBefore設置為false,防止事件被覆蓋,導致complete執行不全。
					complete && info._createListener(Event.COMPLETE, complete.caller, complete.method, complete.args, false, false);
					progress && info._createListener(Event.PROGRESS, progress.caller, progress.method, progress.args, false, false);
				}
			}
			return this;
		}
		
		private function _next():void {
			//@loader滿了則返回等待空閑loader
			if (this._loaderCount >= this.maxLoader) return;
			//@按優先級查找列表
			for (var i:int = 0; i < this._maxPriority; i++) {
				var infos:Array = this._resInfos[i];
				while (infos.length > 0) {
					var info:ResInfo = infos.shift();
					if (info) return _doLoad(info);
				}
			}
			//@所有loader都空閑,則觸發LoaderManager的加載完成
			_loaderCount || event(Event.COMPLETE);
		}
		
		private function _doLoad(resInfo:ResInfo):void {
			this._loaderCount++;
			var loader:Loader = this._loaders.length ? this._loaders.pop() : new Loader();
			loader.on(Event.COMPLETE, null, onLoaded);
			loader.on(Event.PROGRESS, null, function(num:Number):void {
				resInfo.event(Event.PROGRESS, num);
			});
			loader.on(Event.ERROR, null, function(msg:*):void {
				onLoaded(null);
			});
			
			var _this:LoaderManager = this;
			function onLoaded(data:* = null):void {
				//@清理、回收loader
				loader.offAll();
				loader._data = null;
				loader._customParse = false;
				_this._loaders.push(loader);
				_this._endLoad(resInfo, data is Array ? [data] : data);
				_this._loaderCount--;
				_this._next();
			}
			//@具體加載流程在Loader中實現
			loader._class = resInfo.clas;
			loader.load(resInfo.url, resInfo.type, resInfo.cache, resInfo.group, resInfo.ignoreCache);
		}
		
		private function _endLoad(resInfo:ResInfo, content:*):void {
			//如果加載后為空,放入隊列末尾重試
			var url:String = resInfo.url;
			if (content == null) {
				var errorCount:int = this._failRes[url] || 0;
				//@記錄失敗次數
				if (errorCount < this.retryNum) {
					console.warn("[warn]Retry to load:", url);
					this._failRes[url] = errorCount + 1;
					//@重新放回加載隊列
					Laya.timer.once(retryDelay, this, _addReTry, [resInfo], false);
					return;
				} else {
					//超過retryNum則直接拋出ERROR事件
					console.warn("[error]Failed to load:", url);
					event(Event.ERROR, url);
				}
			}
			if (_failRes[url]) _failRes[url] = 0;
			delete _resMap[url];
			//@通知info執行complete方法
			resInfo.event(Event.COMPLETE, content);
			resInfo.offAll();
			_infoPool.push(resInfo);
		}
		
		private function _addReTry(resInfo:ResInfo):void {
			this._resInfos[this._maxPriority - 1].push(resInfo);
			_next();
		}
加載多個資源流程

LoaderManager.load支持傳入簡單類型數組,比如:簡單數組:["a.png","b.png"];復雜數組[{url:"a.png",type:Loader.IMAGE,size:100,priority:1},{url:"b.json",type:Loader.JSON,size:50,priority:1}]。

  • 遍歷所有數組,記錄總數,用於計算progress。記錄下載的總數。
  • 分別調用load方法,加載每一個元素。
  • 每加載完一個資源,檢查加載的數量是否等於本次的總數量,如果是,則執行complete操作。
		/**
		 * @private
		 * 加載數組里面的資源。
		 * @param arr 簡單:["a.png","b.png"],復雜[{url:"a.png",type:Loader.IMAGE,size:100,priority:1},{url:"b.json",type:Loader.JSON,size:50,priority:1}]*/
		private function _loadAssets(arr:Array, complete:Handler = null, progress:Handler = null, type:String = null, priority:int = 1, cache:Boolean = true, group:String = null):LoaderManager {
			var itemCount:int = arr.length;
			var loadedCount:int = 0;
			var totalSize:int = 0;
			var items:Array = [];
			var success:Boolean = true;
			for (var i:int = 0; i < itemCount; i++) {
				var item:Object = arr[i];
				if (item is String) item = {url: item, type: type, size: 1, priority: priority};
				if (!item.size) item.size = 1;
				item.progress = 0;
				totalSize += item.size;
				items.push(item);
				var progressHandler:* = progress ? Handler.create(null, loadProgress, [item], false) : null;
				var completeHandler:* = (complete || progress) ? Handler.create(null, loadComplete, [item]) : null;
				load(item.url, completeHandler, progressHandler, item.type, item.priority || 1, cache, item.group || group);
			}
			
			function loadComplete(item:Object, content:* = null):void {
				loadedCount++;
				item.progress = 1;
				if (!content) success = false;
				if (loadedCount === itemCount && complete) {
					complete.runWith(success);
				}
			}
			
			function loadProgress(item:Object, value:Number):void {
				if (progress != null) {
					item.progress = value;
					var num:Number = 0;
					for (var j:int = 0; j < items.length; j++) {
						var item1:Object = items[j];
						num += item1.size * item1.progress;
					}
					var v:Number = num / totalSize;
					progress.runWith(v);
				}
			}
			return this;
		}
create方法

根據clas類型創建一個未初始化資源的對象,隨后進行異步加載,資源加載完成后,初始化對象的資源,並通過此對象派發 Event.LOADED 事件,事件回調參數值為此對象本身。套嵌資源的子資源會保留資源路徑"?"后的部分。
如果url為數組,返回true;否則返回指定的資源類對象,可以通過偵聽此對象的 Event.LOADED 事件來判斷資源是否已經加載完畢。
注意:cache參數只能對文件后綴為atlas的資源進行緩存控制,其他資源會忽略緩存,強制重新加載。

  • create方法可以先創建一個指定類型的殼,同步返回給調用者。殼里的資源則通過異步的方式加載。

  • create方法主要是在Laya3D中使用,主要是Laya內部組件使用。使用create創建的類型有嚴格限制,在2D下支持Atlas,在3D有一定擴展。

  • 這些類型都要實現ICreateResource方法,提供onAsynLoaded,_setUrl等方法。如果創建一些不支持的類型,會直接拋出異常。

  • 同load方法一樣,支持傳入一個或者多個資源地址。

  • 使用create方法加載Atlas資源時,會返回null。

  • Laya3D所支持的類型:

    //在Laya3D初始化時設置
    var createMap:Object = LoaderManager.createMap;
    createMap["lh"] = [Sprite3D, Laya3D.HIERARCHY];
    createMap["ls"] = [Scene, Laya3D.HIERARCHY];
    createMap["lm"] = [Mesh, Laya3D.MESH];
    createMap["lmat"] = [StandardMaterial, Laya3D.MATERIAL];
    createMap["lpbr"] = [PBRMaterial, Laya3D.MATERIAL];
    createMap["ltc"] = [TextureCube, Laya3D.TEXTURECUBE];
    createMap["jpg"] = [Texture2D, "nativeimage"];
    createMap["jpeg"] = [Texture2D, "nativeimage"];
    createMap["png"] = [Texture2D, "nativeimage"];
    createMap["pkm"] = [Texture2D, Loader.BUFFER];
    createMap["lsani"] = [AnimationTemplet, Loader.BUFFER];
    createMap["lrani"] = [AnimationTemplet, Loader.BUFFER];
    createMap["raw"] = [DataTexture2D, Loader.BUFFER];
    createMap["mipmaps"] = [DataTexture2D, Loader.BUFFER];
    createMap["thdata"] = [TerrainHeightData, Loader.BUFFER];
    createMap["lt"] = [TerrainRes, Laya3D.TERRAIN];
    createMap["lani"] = [AnimationClip, Loader.BUFFER];
    createMap["lav"] = [Avatar, Loader.JSON];
    createMap["ani"] = [AnimationTemplet, Loader.BUFFER];//兼容接口              


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM