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 "StandaloneWindows64";
#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(AssetConfig.RuntimeMode == 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);
}
}
}