using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace Kill.Base { public class NetworkCtrl : MonoBehaviour { public static NetworkCtrl Instance; public string localPath; public string streamingPath; void Awake() { Instance = this; localPath = Application.persistentDataPath; streamingPath = Application.streamingAssetsPath; } async Task Start() { nowResourceUrl = resourceUrls[nowResourceIndex]; #if UNITY_EDITOR_WIN platform = "Windows"; #elif UNITY_EDITOR_OSX platform = "Mac"; #elif UNITY_ANDROID platform = "Android"; #elif UNITY_IOS platform = "iOS"; #endif await Init(); } public async Task Init() { Debug.Log("初始化网络控制"); AndroidStatusBar.statusBarState = AndroidStatusBar.States.TranslucentOverContent; // 检查并清理过期资源 CheckAndCleanOutdatedResources(); if (LoadRes.Instance.resPosition == LoadRes.ResPosition.资源包) { await GetNowBundleList(); if (nowResVersion < new Version(resourceVersion)) { Debug.Log("111"); await GetBundleList(); await UpdateResources(); } } // await FireBaseCtrl.Instance.Init(); await LoadRes.Instance.Init(); UI.UIManager.Instance.Init(); } #region 资源更新 public string[] resourceUrls; public int nowResourceIndex; string nowResourceUrl; public string resourceVersion; string platform; Version nowResVersion = new Version("0.0.0"); public AssetBundleInfoList nowBundleList; public AssetBundleInfoList newBundleList; public async Task GetNowBundleList() { string localListPath = localPath + "/list.json"; string streamingListPath = streamingPath + "/list.json"; // 首先尝试从持久化路径读取 if (File.Exists(localListPath)) { nowBundleList = JsonUtility.FromJson(File.ReadAllText(localListPath)); Debug.Log("===获取本地资源列表成功"); } // 如果本地没有,则从 StreamingAssets 读取 else { string listJson = await ReadTextFileFromStreamingAssets(streamingListPath); if (!string.IsNullOrEmpty(listJson)) { nowBundleList = JsonUtility.FromJson(listJson); Debug.Log("===获取更包资源列表成功"); } else { Debug.Log("===没有获取资源列表"); nowBundleList = null; } } if (nowBundleList != null) { nowResVersion = new Version(nowBundleList.version); } } /// /// 检查并清理过期的本地资源 /// 通过比较版本号判断是否需要清理 /// public async void CheckAndCleanOutdatedResources() { try { // 获取StreamingAssets中的资源列表 string streamingListPath = Path.Combine(streamingPath, "list.json"); string streamingListJson = await ReadTextFileFromStreamingAssets(streamingListPath); if (string.IsNullOrEmpty(streamingListJson)) { return; } AssetBundleInfoList streamingBundleList = JsonUtility.FromJson(streamingListJson); // 获取本地资源列表 string localListPath = Path.Combine(localPath, "list.json"); if (!File.Exists(localListPath)) { // 本地没有资源列表,不需要清理 return; } string localListJson = File.ReadAllText(localListPath); AssetBundleInfoList localBundleList = JsonUtility.FromJson(localListJson); // 比较版本号 if (new Version(streamingBundleList.version) >= new Version(localBundleList.version)) { Debug.Log($"检测到资源版本更新: {localBundleList.version} -> {streamingBundleList.version},正在清理本地过期资源..."); // 版本不一致,清理本地所有.ab文件 CleanLocalAssetBundleFiles(); } else { Debug.Log($"本地资源版本({localBundleList.version})更新,无需清理"); } } catch (Exception ex) { Debug.LogError($"检查资源版本时出错: {ex.Message}"); } } /// /// 清理本地的所有AssetBundle文件 /// private void CleanLocalAssetBundleFiles() { try { // 删除本地所有的.ab文件 string[] localAbFiles = Directory.GetFiles(localPath, "*.ab", SearchOption.AllDirectories); foreach (string abFile in localAbFiles) { try { File.Delete(abFile); Debug.Log($"已删除过期资源文件: {abFile}"); } catch (Exception ex) { Debug.LogError($"删除文件失败 {abFile}: {ex.Message}"); } } File.Delete(localPath + "/list.json"); Debug.Log("本地过期资源清理完成"); } catch (Exception ex) { Debug.LogError($"清理本地资源时出错: {ex.Message}"); } } public async Task GetBundleList() { string url = nowResourceUrl + $"{resourceVersion}/{platform}/list.json"; using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) { var operation = webRequest.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } if (webRequest.result == UnityWebRequest.Result.Success) { AssetBundleInfoList list = JsonUtility.FromJson(webRequest.downloadHandler.text); newBundleList = list; } else { Debug.LogError(url + "获取资源列表失败" + webRequest.error); // ErrorTip.Instance.Init("获取资源列表失败", url, async (msg) => // { // await GetBundleList(); // }); } } } /// /// 从StreamingAssets目录读取文本文件(兼容各平台) /// /// 文件路径 /// 文件内容 private async Task ReadTextFileFromStreamingAssets(string filePath) { string result = null; #if UNITY_ANDROID && !UNITY_EDITOR // Android平台特殊处理 UnityWebRequest www = UnityWebRequest.Get(filePath); var operation = www.SendWebRequest(); // 等待请求完成 while (!operation.isDone) { await Task.Yield(); } if (www.result == UnityWebRequest.Result.Success) { result = www.downloadHandler.text; } else { Debug.LogError($"读取StreamingAssets文件失败: {www.error}"); } www.Dispose(); #else // 其他平台可以直接访问文件系统 if (File.Exists(filePath)) { result = File.ReadAllText(filePath); } #endif return result; } public async Task UpdateResources() { // 获取更新列表 List updateList = GetUpdateList(); if (updateList.Count > 0) { // 开始下载更新 bool success = await DownloadUpdateFiles(updateList, (downloaded, total) => { float progress = (float)downloaded / total; Debug.Log($"更新进度: {progress:P}"); }); if (success) { Debug.Log("资源更新完成"); } else { Debug.LogError("资源更新失败"); } } else { Debug.Log("资源已是最新版本"); } } /// /// 对比当前资源列表和新资源列表,找出需要更新的文件 /// /// 需要更新的资源信息列表 public List GetUpdateList() { List updateList = new List(); // 如果没有当前列表,则全部都需要下载 if (nowBundleList == null || nowBundleList.assetbundles == null) { return newBundleList.assetbundles ?? new List(); } // 创建当前资源字典以便快速查找 Dictionary nowBundleDict = new Dictionary(); foreach (var bundle in nowBundleList.assetbundles) { nowBundleDict[bundle.name] = bundle; } // 对比新资源列表 foreach (var newBundle in newBundleList.assetbundles) { // 检查文件是否存在或MD5是否不匹配 if (!nowBundleDict.ContainsKey(newBundle.name) || nowBundleDict[newBundle.name].md5 != newBundle.md5) { updateList.Add(newBundle); } } return updateList; } /// /// 计算需要更新的总大小 /// /// 需要更新的资源列表 /// 总大小(字节) public long CalculateUpdateSize(List updateList) { long totalSize = 0; foreach (var bundle in updateList) { totalSize += bundle.size; } return totalSize; } /// /// 下载更新文件并实时返回进度 /// /// 需要更新的资源列表 /// 进度回调(已下载字节数, 总字节数) /// 是否下载成功 public async Task DownloadUpdateFiles(List updateList, Action progressCallback) { long totalSize = CalculateUpdateSize(updateList); long downloadedSize = 0; // 初始化 nowBundleList(如果为空) if (nowBundleList == null || nowBundleList.assetbundles == null) { nowBundleList = new AssetBundleInfoList(new List()); } // 创建当前资源字典以便快速查找和更新 Dictionary nowBundleDict = new Dictionary(); foreach (var bundle in nowBundleList.assetbundles) { nowBundleDict[bundle.name] = bundle; } foreach (var bundle in updateList) { string url = nowResourceUrl + $"{resourceVersion}/{platform}/{bundle.name}"; string savePath = Path.Combine(localPath, bundle.name); // 确保目录存在 string directory = Path.GetDirectoryName(savePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } bool downloadSuccess = await DownloadSingleFile(url, savePath, bundle.md5); if (!downloadSuccess) { Debug.LogError($"下载文件失败: {bundle.name}"); return false; } // 下载成功后,更新 nowBundleList nowBundleDict[bundle.name] = bundle; downloadedSize += bundle.size; progressCallback?.Invoke(downloadedSize, totalSize); // 简短延迟避免请求过于频繁 await Task.Yield(); } // 更新完整的 nowBundleList nowBundleList.assetbundles = new List(nowBundleDict.Values); nowBundleList.version = resourceVersion; // 下载完成后保存新的list.json SaveNewBundleList(); return true; } /// /// 下载单个文件并校验MD5 /// /// 文件URL /// 保存路径 /// 期望的MD5值 /// 是否下载并校验成功 private async Task DownloadSingleFile(string url, string savePath, string expectedMd5) { int maxRetries = 2; for (int i = 0; i <= maxRetries; i++) { using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) { var operation = webRequest.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } if (webRequest.result == UnityWebRequest.Result.Success) { // 保存文件 File.WriteAllBytes(savePath, webRequest.downloadHandler.data); // 校验MD5 string actualMd5 = CalculateMD5(savePath); if (actualMd5.ToLower() == expectedMd5.ToLower()) { return true; } else { Debug.LogError($"MD5校验失败 {url}: 期望 {expectedMd5}, 实际 {actualMd5}"); // MD5校验失败时删除文件 if (File.Exists(savePath)) { File.Delete(savePath); } } } else { Debug.LogError($"下载失败 {url}: {webRequest.error} (尝试 {i + 1}/{maxRetries + 1})"); } // 如果还没达到最大重试次数,等待一段时间后重试 if (i < maxRetries) { await Task.Delay(1000 * (i + 1)); // 递增延迟时间 } } } // 所有重试都失败后显示错误提示 ErrorTip.Instance.Init("下载资源包失败", url, async (msg) => { // 错误处理回调 - 重新尝试下载更新资源流程 Debug.Log("用户选择重试下载资源包"); await UpdateResources(); }); return false; } /// /// 计算文件的MD5值 /// /// 文件路径 /// MD5值 private string CalculateMD5(string filePath) { using (var md5 = System.Security.Cryptography.MD5.Create()) { using (var stream = File.OpenRead(filePath)) { byte[] hash = md5.ComputeHash(stream); return BitConverter.ToString(hash).Replace("-", "").ToLower(); } } } /// /// 保存新的资源列表到本地 /// public void SaveNewBundleList() { if (newBundleList != null) { string json = JsonUtility.ToJson(newBundleList, true); string savePath = Path.Combine(localPath, "list.json"); File.WriteAllText(savePath, json); Debug.Log("新的资源列表已保存到本地"); } } #endregion #region 通用网络请求 #endregion } }