using TFramework; using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UnityEngine.Networking; using Cysharp.Threading.Tasks; using Newtonsoft.Json.Serialization; using System.Threading.Tasks; using System.IO; using System.Reflection; #if HybridCLR using HybridCLR; #endif namespace TModule.Runtime { public class HotfixManager : BaseManager { /// /// 是否开启热更 /// [SerializeField] bool isHotfix; /// /// 资源下载地址 /// [SerializeField] string resServerUrl; /// /// 资源加载模式 /// [SerializeField] UpdateMode loadMode; /// /// 热更新dll名称 /// [SerializeField] string hotfixDllName; /// /// 热更新启动类 /// [SerializeField] string hotfixClass; /// /// 超时时间 /// private const int DOWNLOADTIMEOUT = 5; /// /// 获取平台 /// public string GetPlatform { get { #if UNITY_ANDROID return "Android"; #elif UNITY_IOS return "IOS"; #elif UNITY_STANDALONE_OSX return "Mac"; #elif UNITY_STANDALONE_WIN return "Windows"; #elif UNITY_WEBGL return "WebGL"; #else return Application.platform.ToString(); #endif } } public long TotalSize { get { long szie = 0; _NeedDownloadResList.ForEach(p => szie += (long)p.m_size); return szie; } } TAction onCheckUpdataEvent; TAction onCheckOverEvent; TAction onErrorEvent; /// /// 检查进度 /// private float _checkProgress; private string _loaclVersionPath; private List _NeedDownloadResList = new List(); private List _LocalResList = new List(); private long completeTotalCount = 0; /// ///本地资源存放路径 /// string localResFilePath; /// /// 版本文件名称 /// string versionFileName = "VersionFile.json"; /// /// 热更入口 /// /// 更新事件 return 返回检查更新进度(仅进度)/更新进度(进度,总大小,已下载大小) /// 检查完成 return 是否有更新 更新资源大小 /// 热更出错事件 public void Startup(TAction onUpdata, TAction onCheckOver,TAction onError=null) { localResFilePath = Application.persistentDataPath + "/"; resServerUrl += '/' + GetPlatform; AssetConfig.AssetBundleManifestName = GetPlatform; #if !UNITY_EDITOR AssetConfig.RuntimeMode = UpdateMode.Bulid; #else AssetConfig.RuntimeMode = loadMode; #endif onCheckUpdataEvent = onUpdata; onCheckOverEvent = onCheckOver; onErrorEvent = onError; if (isHotfix) { if (AssetConfig.RuntimeMode == UpdateMode.Bulid) { CheckUpdata(); } else { EnterAPP(); onCheckOverEvent(null, 0); } } else { EnterAPP(); onCheckOverEvent(null, 0); } } /// /// 检查更新 /// private async UniTask CheckUpdata() { if (string.IsNullOrEmpty(resServerUrl)) { Log.Error("未指定资源服务器地址"); return; } string strVersionPath = resServerUrl + "/" + versionFileName; List versionDatas = await DowloadVersion(strVersionPath); if(versionDatas!=null) { OnInitVersionCallBack(versionDatas); if (_NeedDownloadResList.Count > 0) { onCheckOverEvent?.Invoke(DownloadFiles, TotalSize); } else { EnterAPP(); onCheckOverEvent?.Invoke(null, 0); } } } public async void EnterAPP() { await UniTask.DelayFrame(2); Assembly Csharp; if(string.IsNullOrEmpty(hotfixDllName)) { Log.Error("开启了热更新,但未指定热更程序集,程序终止"); #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; #endif return; } if(loadMode==UpdateMode.Editor||!isHotfix) { Csharp = AppDomain.CurrentDomain.Load(hotfixDllName); } else { LoadMetadataForAOTAssemblies(); byte[] assemblyDll = FileOperations.SafeReadAllBytes(AssetConfig.AssetBundleRootPath + $"{hotfixDllName}.dll.bytes"); if (assemblyDll == null) { Log.Error($"未加载到{hotfixDllName}.dll.bytes文件,请检查文件是否存在"); return; } Csharp = Assembly.Load(assemblyDll); } if (string.IsNullOrEmpty(hotfixClass)) { Log.Error("开启了热更新,但未指定热更入口程序"); return; } object hclass = Csharp.CreateInstance(hotfixClass); if (hclass != null && (hclass as IHotfixStartup) != null) { (hclass as IHotfixStartup).Run(); } else Log.Error($"热更入口程序{hotfixClass}未找到,{hotfixClass}需要继承自IHotfixStartup,请检查脚本"); } /// /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。 /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行 /// private static void LoadMetadataForAOTAssemblies() { List aotMetaAssemblyFiles = Resources.Load("HotfixUpdate/热更配置表").m_hotfixUpdateAotDlls; /// 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。 /// 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误 /// #if HybridCLR HomologousImageMode mode = HomologousImageMode.SuperSet; foreach (var aotDllName in aotMetaAssemblyFiles) { byte[] dllBytes = FileOperations.SafeReadAllBytes(AssetConfig.AssetBundleRootPath + aotDllName + ".bytes"); Debug.Log($"加载{aotDllName}******{dllBytes}"); // 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码 LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode); if (err != LoadImageErrorCode.OK) { Debug.LogError($"Error occurs when loadMetadataForAOT: {err}"); } else Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. mode:{mode} ret:{err}"); } #else Log.Error("当前非热更环境!请先安装HybridCLR"); #endif } /// /// 下载版本文件 /// /// /// /// private async UniTask> DowloadVersion(string url) { UnityWebRequest www = UnityWebRequest.Get(url); www.timeout = DOWNLOADTIMEOUT; await www.SendWebRequest(); while (!www.isDone) { _checkProgress = www.downloadProgress / 2 * 0.1f; onCheckUpdataEvent?.Invoke(_checkProgress,0,0); } if (www.result != UnityWebRequest.Result.ProtocolError && www.result != UnityWebRequest.Result.ConnectionError) { string content = www.downloadHandler.text; return content.JsonStrToObject>(); } else { Debug.Log("下载失败:" + www.error); onErrorEvent?.Invoke(www.error); return null; } } /// /// 对比版本文件 /// /// private void OnInitVersionCallBack(List arg0) { _loaclVersionPath = localResFilePath + versionFileName; if (FileOperations.FileExists(_loaclVersionPath)) { List clienData = FileOperations.ReadJsonData>(_loaclVersionPath); _LocalResList.AddRange(clienData); foreach (var item in arg0) { var progress = 1.0f / arg0.Count * 0.9f + _checkProgress; progress = progress >= 1 ? 1 : progress; onCheckUpdataEvent?.Invoke(progress,0,0); VersionDataEntity dataEntity = clienData.Find(p => p.m_fullName == item.m_fullName); //已有资源 if (dataEntity != null) { //对比MD5 if (dataEntity.m_md5 != item.m_md5) _NeedDownloadResList.Add(item); } else _NeedDownloadResList.Add(item);//新资源 } } else arg0.ForEach(p => { _NeedDownloadResList.Add(p); var progress = 1.0f / arg0.Count * 0.9f + _checkProgress; progress = progress >= 1 ? 1 : progress; onCheckUpdataEvent?.Invoke(progress,0,0); }); } /// /// 更新版本文件 /// /// private void ModifyLocaData(VersionDataEntity entity) { if (_LocalResList == null) return; bool isExists = false; for (int i = 0; i < _LocalResList.Count; i++) { if (_LocalResList[i].m_fullName.Equals(entity.m_fullName, StringComparison.CurrentCultureIgnoreCase)) { _LocalResList[i].m_md5 = entity.m_md5; _LocalResList[i].m_fullName = entity.m_fullName; _LocalResList[i].m_size = entity.m_size; isExists = true; break; } } if (!isExists) { _LocalResList.Add(entity); } SaveLoaclVersion(); } /// /// 保存版本文件 /// private void SaveLoaclVersion() => FileOperations.WriteJsonData(_LocalResList, _loaclVersionPath); /// /// 分配下载任务并开始下载 /// /// private void DownloadFiles() { _checkProgress = 0; foreach (var versionData in _NeedDownloadResList) { string savePath = Path.Combine(localResFilePath, versionData.m_fullName); savePath = savePath.Replace("\\", "/"); string url = resServerUrl+"/"+ versionData.m_fullName; if (File.Exists(savePath)) File.Delete(savePath); Main.GetMagr().StartDownload(url, savePath, OnDownloading, () => { completeTotalCount += (long)versionData.m_size; ModifyLocaData(versionData); if (completeTotalCount >= TotalSize) { EnterAPP(); _checkProgress = 1; onCheckUpdataEvent?.Invoke(_checkProgress, TotalSize, completeTotalCount); } }, err => onErrorEvent?.Invoke(err)); } } long t = 0; private void OnDownloading(float prossage,long totaSize,long size) { _checkProgress = (completeTotalCount*1.0f)/TotalSize + 1/TotalSize*(size*1.0f / totaSize); onCheckUpdataEvent?.Invoke(_checkProgress, TotalSize, completeTotalCount); } } }