killapp/Assets/Scripts/Base/NetworkCtrl.cs
2026-03-30 16:25:00 +08:00

488 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<AssetBundleInfoList>(File.ReadAllText(localListPath));
Debug.Log("===获取本地资源列表成功");
}
// 如果本地没有,则从 StreamingAssets 读取
else
{
string listJson = await ReadTextFileFromStreamingAssets(streamingListPath);
if (!string.IsNullOrEmpty(listJson))
{
nowBundleList = JsonUtility.FromJson<AssetBundleInfoList>(listJson);
Debug.Log("===获取更包资源列表成功");
}
else
{
Debug.Log("===没有获取资源列表");
nowBundleList = null;
}
}
if (nowBundleList != null)
{
nowResVersion = new Version(nowBundleList.version);
}
}
/// <summary>
/// 检查并清理过期的本地资源
/// 通过比较版本号判断是否需要清理
/// </summary>
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<AssetBundleInfoList>(streamingListJson);
// 获取本地资源列表
string localListPath = Path.Combine(localPath, "list.json");
if (!File.Exists(localListPath))
{
// 本地没有资源列表,不需要清理
return;
}
string localListJson = File.ReadAllText(localListPath);
AssetBundleInfoList localBundleList = JsonUtility.FromJson<AssetBundleInfoList>(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}");
}
}
/// <summary>
/// 清理本地的所有AssetBundle文件
/// </summary>
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<AssetBundleInfoList>(webRequest.downloadHandler.text);
newBundleList = list;
}
else
{
Debug.LogError(url + "获取资源列表失败" + webRequest.error);
// ErrorTip.Instance.Init("获取资源列表失败", url, async (msg) =>
// {
// await GetBundleList();
// });
}
}
}
/// <summary>
/// 从StreamingAssets目录读取文本文件兼容各平台
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns>文件内容</returns>
private async Task<string> 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<AssetBundleInfo> 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("资源已是最新版本");
}
}
/// <summary>
/// 对比当前资源列表和新资源列表,找出需要更新的文件
/// </summary>
/// <returns>需要更新的资源信息列表</returns>
public List<AssetBundleInfo> GetUpdateList()
{
List<AssetBundleInfo> updateList = new List<AssetBundleInfo>();
// 如果没有当前列表,则全部都需要下载
if (nowBundleList == null || nowBundleList.assetbundles == null)
{
return newBundleList.assetbundles ?? new List<AssetBundleInfo>();
}
// 创建当前资源字典以便快速查找
Dictionary<string, AssetBundleInfo> nowBundleDict = new Dictionary<string, AssetBundleInfo>();
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;
}
/// <summary>
/// 计算需要更新的总大小
/// </summary>
/// <param name="updateList">需要更新的资源列表</param>
/// <returns>总大小(字节)</returns>
public long CalculateUpdateSize(List<AssetBundleInfo> updateList)
{
long totalSize = 0;
foreach (var bundle in updateList)
{
totalSize += bundle.size;
}
return totalSize;
}
/// <summary>
/// 下载更新文件并实时返回进度
/// </summary>
/// <param name="updateList">需要更新的资源列表</param>
/// <param name="progressCallback">进度回调(已下载字节数, 总字节数)</param>
/// <returns>是否下载成功</returns>
public async Task<bool> DownloadUpdateFiles(List<AssetBundleInfo> updateList, Action<long, long> progressCallback)
{
long totalSize = CalculateUpdateSize(updateList);
long downloadedSize = 0;
// 初始化 nowBundleList如果为空
if (nowBundleList == null || nowBundleList.assetbundles == null)
{
nowBundleList = new AssetBundleInfoList(new List<AssetBundleInfo>());
}
// 创建当前资源字典以便快速查找和更新
Dictionary<string, AssetBundleInfo> nowBundleDict = new Dictionary<string, AssetBundleInfo>();
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<AssetBundleInfo>(nowBundleDict.Values);
nowBundleList.version = resourceVersion;
// 下载完成后保存新的list.json
SaveNewBundleList();
return true;
}
/// <summary>
/// 下载单个文件并校验MD5
/// </summary>
/// <param name="url">文件URL</param>
/// <param name="savePath">保存路径</param>
/// <param name="expectedMd5">期望的MD5值</param>
/// <returns>是否下载并校验成功</returns>
private async Task<bool> 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;
}
/// <summary>
/// 计算文件的MD5值
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns>MD5值</returns>
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();
}
}
}
/// <summary>
/// 保存新的资源列表到本地
/// </summary>
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
}
}