123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- 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
- {
- /// <summary>
- /// 是否开启热更
- /// </summary>
- [SerializeField] bool isHotfix;
- /// <summary>
- /// 资源下载地址
- /// </summary>
- [SerializeField] string resServerUrl;
- /// <summary>
- /// 资源加载模式
- /// </summary>
- [SerializeField] UpdateMode loadMode;
- /// <summary>
- /// 热更新dll名称
- /// </summary>
- [SerializeField] string hotfixDllName;
- /// <summary>
- /// 热更新启动类
- /// </summary>
- [SerializeField] string hotfixClass;
- /// <summary>
- /// 超时时间
- /// </summary>
- private const int DOWNLOADTIMEOUT = 5;
- /// <summary>
- /// 获取平台
- /// </summary>
- 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<float,long,long> onCheckUpdataEvent;
- TAction<TAction, long> onCheckOverEvent;
- TAction<string> onErrorEvent;
- /// <summary>
- /// 检查进度
- /// </summary>
- private float _checkProgress;
- private string _loaclVersionPath;
- private List<VersionDataEntity> _NeedDownloadResList = new List<VersionDataEntity>();
- private List<VersionDataEntity> _LocalResList = new List<VersionDataEntity>();
- private long completeTotalCount = 0;
- /// <summary>
- ///本地资源存放路径
- /// </summary>
- string localResFilePath;
- /// <summary>
- /// 版本文件名称
- /// </summary>
- string versionFileName = "VersionFile.json";
- /// <summary>
- /// 热更入口
- /// </summary>
- /// <param name="onCheckUpdata">更新事件 return 返回检查更新进度(仅进度)/更新进度(进度,总大小,已下载大小)</param>
- /// <param name="onCheckOver">检查完成 return 是否有更新 更新资源大小</param>
- /// <param name="onError">热更出错事件</param>
- public void Startup(TAction<float,long,long> onUpdata, TAction<TAction, long> onCheckOver,TAction<string> 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);
- }
- }
- /// <summary>
- /// 检查更新
- /// </summary>
- private async UniTask CheckUpdata()
- {
- if (string.IsNullOrEmpty(resServerUrl))
- {
- Log.Error("未指定资源服务器地址");
- return;
- }
- string strVersionPath = resServerUrl + "/" + versionFileName;
- List<VersionDataEntity> 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,请检查脚本");
- }
- /// <summary>
- /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。
- /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行
- /// </summary>
- private static void LoadMetadataForAOTAssemblies()
- {
- List<string> aotMetaAssemblyFiles = Resources.Load<HotfixConfig>("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
- }
- /// <summary>
- /// 下载版本文件
- /// </summary>
- /// <param name="url"></param>
- /// <param name="OnDownLoadOver"></param>
- /// <returns></returns>
- private async UniTask<List<VersionDataEntity>> 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<List<VersionDataEntity>>();
- }
- else
- {
- Debug.Log("下载失败:" + www.error);
- onErrorEvent?.Invoke(www.error);
- return null;
- }
- }
- /// <summary>
- /// 对比版本文件
- /// </summary>
- /// <param name="arg0"></param>
- private void OnInitVersionCallBack(List<VersionDataEntity> arg0)
- {
- _loaclVersionPath = localResFilePath + versionFileName;
- if (FileOperations.FileExists(_loaclVersionPath))
- {
- List<VersionDataEntity> clienData = FileOperations.ReadJsonData<List<VersionDataEntity>>(_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);
- });
- }
- /// <summary>
- /// 更新版本文件
- /// </summary>
- /// <param name="entity"></param>
- 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();
- }
- /// <summary>
- /// 保存版本文件
- /// </summary>
- private void SaveLoaclVersion() => FileOperations.WriteJsonData(_LocalResList, _loaclVersionPath);
- /// <summary>
- /// 分配下载任务并开始下载
- /// </summary>
- /// <param name="downloadList"></param>
- 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<DownloadManager>().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);
- }
- }
- }
|