using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using UnityEngine; namespace AssetManagement { /// /// Blastom: 管理整个项目的资源路径,资源是否存在等问题 /// public class AssetPathHub { // 当前版本可用的streamingAsset资源 public readonly HashSet streamingAssets = new HashSet(); private FileStream _fileStream; private BinaryFormatter _formatter; private float _lastSaveTime; private const float _autoSaveInterval = 300f; // 网络透传的资源情况 public Dictionary onlineAssets; // 当前版本persistent资源情况 public Dictionary persistAssets; private static string persistPath { get { return AssetConst.persistentDataPath.Open(AssetConst.pathFile + AssetConst.bytesExtension); } } // 下载路径超链接 public string downloadUrl { get; private set; } // 执行三项对比,确认每个资源的位置 // 简单处理。本身消耗不高,不进行额外优化 public void Init(string uri, AssetDependencyInfo manifest) { _formatter = new BinaryFormatter(); var streamingBytes = Resources.Load(AssetConst.assetPathDataPath.Open(AssetConst.pathFile)).bytes; Dictionary streamAssets; using (var ms = new MemoryStream(streamingBytes)) { streamAssets = _formatter.Deserialize(ms) as Dictionary; } if (streamAssets == null) throw new NullReferenceException("Failed to load streamingPathInfo, this is not recoverable!"); if (File.Exists(persistPath)) using (var fs = File.OpenRead(persistPath)) { try { persistAssets = _formatter.Deserialize(fs) as Dictionary; } catch (Exception e) { Debug.LogError(e); } } downloadUrl = uri; onlineAssets = new Dictionary(); foreach (var item in manifest.assetList) onlineAssets.Add(item.name, item); // 注:Persist资源不主动移除 // 因此不能随意排除当前不匹配的项目 if (persistAssets == null) persistAssets = new Dictionary(); // if (EngineManager.IsLocalRes) // foreach (var item in manifest.assetList) // streamingAssets.Add(item.name); // else // 填充StreamingAsset的情况 // 这个是整个apk阶段都不变的 foreach (var asset in streamAssets) { AssetDependencyItem online; if (onlineAssets.TryGetValue(asset.Key, out online) && online.md5 == asset.Value) streamingAssets.Add(asset.Key); } } public AssetDependencyItem GetPathData(string bundleName) { AssetDependencyItem online; onlineAssets.TryGetValue(bundleName, out online); return online; } /// /// 获得后台下载资源列表 /// public List GetBackgroundDownloadList() { var result = new List(); foreach (var item in onlineAssets.Values) if (GetPathState(item.name) == AssetPathState.online && item.level > 0) result.Add(item); result.Sort((a, b) => a.importance.CompareTo(b.importance)); return result; } public string[] GetAllDependencies(string bundleName) { AssetDependencyItem online; var result = onlineAssets.TryGetValue(bundleName, out online) ? online.dependencies : null; return result; } public int GetPathState(string bundleName) { var result = AssetPathState.unmarked; if (streamingAssets.Contains(bundleName)) { result = AssetPathState.streaming; } else { AssetDependencyItem online; if (onlineAssets.TryGetValue(bundleName, out online)) { string md5; if (persistAssets.TryGetValue(bundleName, out md5)) result = md5 == online.md5 ? AssetPathState.persist : AssetPathState.online; else result = AssetPathState.online; } } return result; } public string GetPersistPath(string bundleName) { return AssetConst.GetPersistBundle(bundleName); } /// /// 强制获得在线路径 /// public string GetDownloadPath(string bundleName) { string result; AssetDependencyItem online; if (onlineAssets.TryGetValue(bundleName, out online)) result = downloadUrl.Open(online.version.ToString()).Open(bundleName); else { Debug.LogError("Unable to get path for unmarked asset " + bundleName); result = string.Empty; } return result; } public string GetPath(string bundleName) { var state = 0; string result; AssetDependencyItem online; if (onlineAssets.TryGetValue(bundleName, out online)) state = GetPathState(bundleName); switch (state) { case AssetPathState.online: // ReSharper disable once PossibleNullReferenceException result = downloadUrl.Open(online.version.ToString()).Open(bundleName); break; case AssetPathState.persist: result = GetPersistPath(bundleName); break; case AssetPathState.streaming: result = AssetConst.streamingAssetsPath.Open(bundleName); break; default: Debug.LogError("Unable to get path for unmarked asset " + bundleName); result = string.Empty; break; } return result; } /// /// 加载资源失败的情况下,回滚当前Persist的记录 /// public void LoadError(string bundle) { if (persistAssets.ContainsKey(bundle)) persistAssets.Remove(bundle); else Debug.LogError(string.Format("Failed to load a non persist asset {0}! Could be some problem on I/O!", bundle)); // SavePersistPathData(); } /// /// 下载器下载成功后,通知更新资源配置 /// public void DownloadComplete(BundleDownloader unit) { if (string.IsNullOrEmpty(unit.error)) { AssetDependencyItem online; if (onlineAssets.TryGetValue(unit.name, out online)) { persistAssets[online.name] = online.md5; if (_autoSaveInterval + Time.realtimeSinceStartup < _lastSaveTime) SavePersistPathData(); } else Debug.LogError(string.Format( "Download an asset bundle {0}, which is not required for current version!", unit.name)); } } public void SavePersistPathData() { _lastSaveTime = Time.realtimeSinceStartup; // 注:不创建的资源列表,防止出现问题 if (persistAssets.Count > 0) { if (_fileStream == null) _fileStream = File.Open(persistPath, FileMode.OpenOrCreate); if (_fileStream != null) try { _fileStream.SetLength(0); _formatter.Serialize(_fileStream, persistAssets); } catch (Exception e) { Debug.LogError("Failed to Write to FileStream:\n" + e); } else Debug.LogError(string.Format("Failed to Open FileStream at path {0}", persistPath)); } } public void Dispose() { if (_fileStream != null) { _fileStream.Dispose(); _fileStream = null; } } } // 使用整数以提高Lua互用能力 public static class AssetPathState { // 注:避开default(int) public const int streaming = 1; public const int persist = 2; public const int online = 3; public const int unmarked = 4; } }