上一篇介绍了HybridCLR的用法,这一篇讲一下通过Addressable 来下载热更dll 以及需要补充的元数据dll ,我们知道unity 是无法直接识别dll 文件,需要把那些dll 加上.byte 后缀,所以需要写个工具转换一下,首选我在Asset 下面新建HotUpdateDlls 文件夹,然后再分别建HotUpdateDll(存放热更dll 加byte后缀)MetaDataDll(存放元数据Aot 的dll 加byte后缀)
1、话不多说,直接上工具代码
static string HotUpdateDllPath => $"{Application.dataPath}/../HybridCLRData/HotUpdateDlls/{EditorUserBuildSettings.activeBuildTarget}/";
static string HotUpdateDestinationPath => $"{Application.dataPath}/HotUpdateDlls/HotUpdateDll/";
static string MetaDataDLLPath => $"{Application.dataPath}/../HybridCLRData/AssembliesPostIl2CppStrip/{EditorUserBuildSettings.activeBuildTarget}/";
static string MetaDataDestinationPath => $"{Application.dataPath}/HotUpdateDlls/MetaDataDll/";
static string AOTGenericReferencesPath => $"{Application.dataPath}/HybridCLRGenerate/AOTGenericReferences.cs";
[MenuItem("Build/第一次GenerateAll拷贝热更dll以及元数据dll")]
private static void HybridCLRCopyDll()
{
// 是否有安装HybridCLR
var controller = new InstallerController();
if (!controller.HasInstalledHybridCLR())
{
Debug.LogError("HybridCLR is not Installer");
return;
}
//执行HybridCLR
PrebuildCommand.GenerateAll();
CopyHotUpdateDll();
CopyMetaDataDll();
}
[MenuItem("Build/CompileDllActiveBuildTarget拷贝以及热更dll")]
private static void HybridCLRHofixCopyDll()
{
// 是否有安装HybridCLR
var controller = new InstallerController();
if (!controller.HasInstalledHybridCLR())
{
Debug.LogError("HybridCLR is not Installer");
return;
}
CompileDllCommand.CompileDllActiveBuildTarget();
CopyHotUpdateDll();
}
private static void CopyHotUpdateDll()
{
var assemblies = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
var dir = new DirectoryInfo(HotUpdateDllPath);
var files = dir.GetFiles();
var destDir = HotUpdateDestinationPath;
if (Directory.Exists(destDir))
Directory.Delete(destDir, true);
Directory.CreateDirectory(destDir);
foreach (var file in files)
{
if (file.Extension == ".dll" && assemblies.Contains(file.Name.Substring(0, file.Name.Length - 4)))
{
var desPath = destDir + file.Name + ".bytes";
file.CopyTo(desPath, true);
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("copy hot update dlls success!");
}
private static void CopyMetaDataDll()
{
List<string> assemblies = GetMetaDataDllList();
var dir = new DirectoryInfo(MetaDataDLLPath);
var files = dir.GetFiles();
var destDir = MetaDataDestinationPath;
if (Directory.Exists(destDir))
Directory.Delete(destDir, true);
Directory.CreateDirectory(destDir);
foreach (var file in files)
{
if (file.Extension == ".dll" && assemblies.Contains(file.Name))
{
var desPath = destDir + file.Name + ".bytes";
file.CopyTo(desPath, true);
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("copy meta data dll success!");
}
private static List<string> GetMetaDataDllList()
{
var aotGenericRefPath = AOTGenericReferencesPath;
List<string> result = new List<string>();
using (StreamReader reader = new StreamReader(aotGenericRefPath))
{
var lineStr = "";
while (!reader.ReadLine().Contains("new List<string>"))
{
}
reader.ReadLine();
while (true)
{
lineStr = reader.ReadLine().Replace("\t", "");
if (lineStr.Contains("};"))
break;
var dllName = lineStr.Substring(1, lineStr.Length - 3);
result.Add(dllName);
}
}
return result;
}
这个工具干的工作 就是把生成的dll 放到指定的文件夹下 ,要说一下,元数据dll 其实是读的HybridCLRGenerate/AOTGenericReferences 里面的PatchedAOTAssemblyList
2,首先建两个场景,一个是 GameLaunher 启动场景,属于Aot(在Scenes In Build 勾选上),另一个是GameStart 场景,GameStart 是热更新完成后首个加载的热更新场景,只需要把热更脚本 挂载在这个场景上就行了 ,而且还不需要任何反射操作,也是官方推荐的做法
3、来看一下 Addressable 的分组,直接把所对应的文件夹拖上去就行了
4、先建一个热更程序集,看下图
5、首先在GameLaunher 新建一个进度条 ,然后挂上Launher 启动脚本
直接看Launher 脚本
private byte[] _dllBytes;
public Slider slider;
void Start()
{
StartCoroutine(GameLaunch());
}
private IEnumerator GameLaunch()
{
yield return DoUpdateAddressadble(); // 加载ab
yield return LoadDll();
yield return EnterGame();
}
IEnumerator DoUpdateAddressadble()
{
AsyncOperationHandle<IResourceLocator> initHandle = Addressables.InitializeAsync();
yield return initHandle;
// 检测更新
var checkHandle = Addressables.CheckForCatalogUpdates(false);
yield return checkHandle;
if (checkHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError($"CheckForCatalogUpdates Error{checkHandle.OperationException.ToString()}");
yield break;
}
if (checkHandle.Result.Count > 0)
{
var updateHandle = Addressables.UpdateCatalogs(checkHandle.Result, false);
yield return updateHandle;
if (updateHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError($"UpdateCatalogs Error{updateHandle.OperationException.ToString()}");
yield break;
}
// 更新列表迭代器
List<IResourceLocator> locators = updateHandle.Result;
foreach (var locator in locators)
{
List<object> keys = new List<object>();
foreach (var key in locator.Keys)
{
if (key is string)
{
keys.Add(key);
}
}
var sizeHandle = Addressables.GetDownloadSizeAsync(keys);
yield return sizeHandle;
if (sizeHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError($"GetDownloadSizeAsync Error{sizeHandle.OperationException.ToString()}");
yield break;
}
long totalDownloadSize = sizeHandle.Result;
Debug.Log("download size : " + totalDownloadSize);
if (totalDownloadSize > 0)
{
// 下载
var downloadHandle = Addressables.DownloadDependenciesAsync(keys, Addressables.MergeMode.Union);
while (!downloadHandle.IsDone)
{
if (downloadHandle.Status == AsyncOperationStatus.Failed)
{
Debug.LogError($"DownloadDependenciesAsync Error{downloadHandle.OperationException.ToString()}");
yield break;
}
// 下载进度
float percentage = downloadHandle.PercentComplete;
Debug.Log($"已下载: {percentage}");
slider.value = percentage;
yield return null;
}
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log("下载完毕!");
slider.value = 1f;
}
}
else
{
Debug.Log("无下载");
slider.value = 1f;
}
}
}
else
{
Debug.Log("没有检测到更新!");
slider.value = 1f;
}
// 进游戏
}
private IEnumerator LoadDll()
{
yield return LoadMetadataForAOTAssemblies();
yield return LoadGameHotUpdateDll();
yield return ReloadAddressableCatalog();
Debug.Log("LoadAssemblies finish!");
yield return null;
}
private IEnumerator LoadGameHotUpdateDll()
{
var path = $"Assets/HotUpdateDlls/HotUpdateDll/GameHotUpdate.dll.bytes";
ReadDllBytes(path);
if (_dllBytes != null)
{
var assembly = Assembly.Load(_dllBytes);
Debug.Log($"Load Assembly success,assembly Name:{assembly.FullName}");
}
yield return null;
}
//补充元数据
private IEnumerator LoadMetadataForAOTAssemblies()
{
List<string> aotDllList = new List<string>
{
"mscorlib.dll",
//"System.dll",
//"System.Core.dll", // 如果使用了Linq,需要这个
};
foreach (var aotDllName in aotDllList)
{
var path = $"Assets/HotUpdateDlls/MetaDataDll/{aotDllName}.bytes";
ReadDllBytes(path);
if (_dllBytes != null)
{
var err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(_dllBytes, HomologousImageMode.SuperSet);
Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. ret:{err}");
}
}
yield return null;
Debug.Log("LoadMetadataForAOTAssemblies finish!");
}
private void ReadDllBytes(string path)
{
var dllText = LoadAsset<TextAsset>(path);
if (dllText == null)
{
Debug.LogError($"cant load dllText,path:{path}");
_dllBytes = null;
}
else
{
_dllBytes = dllText.bytes;
}
UnloadAsset(dllText);
}
#region 为了方便 所以写到这里了
public T LoadAsset<T>(string path)
{
var op = Addressables.LoadAssetAsync<T>(path);
if (!op.IsValid())
return default;
op.WaitForCompletion();
return op.Result;
}
public IEnumerator LoadScene(string sceneName)
{
var op = Addressables.LoadSceneAsync(sceneName, LoadSceneMode.Single);
yield return op;
if (op.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError(
$"load scene failed,exception:{op.OperationException.Message} \r\n {op.OperationException.StackTrace}");
}
}
public void UnloadAsset(UnityEngine.Object asset)
{
if (asset != null)
Addressables.Release(asset);
}
// 加载完dll 要 重新加载catalog
private IEnumerator ReloadAddressableCatalog()
{
var op = Addressables.LoadContentCatalogAsync($"{Addressables.RuntimePath}/catalog.json");
yield return op;
if (op.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError(
$"load content catalog failed, exception:{op.OperationException.Message} \r\n {op.OperationException.StackTrace}");
}
}
private IEnumerator EnterGame()
{
yield return LoadScene("Assets/Scenes/GameScenes/GameStart.unity");
Debug.Log("EnterGame");
}
#endregion
注意:ReloadAddressableCatalog 这个方法 在加载完dll 跑一下,请看官网所提到的bug
如果想看更详细的,请看这位大佬写的博客 ok,剩下的 就按照Addressable 的打包资源流程走就行了 ,我这边 安卓和IOS 都跑一遍没问题,有啥不对的,欢迎小伙伴来指正哈