HybridCLR 热更新笔记 GF接入

官方文档

实验环境

Unity 版本(国际版): Unity 2020.3.26.f1
hybridclr_unity : https://gitee.com/focus-creative-games/hybridclr_unity.git#v2.0.0-rc

安装HybridCLR (有gitee账号才可使用)

官方安装教程

添加 hybridclr_unity package

  • 方法一:
    在 Unity项目/Packages/manifest.json 文件中添加一行(注意逗号, 末尾行不需要在后面加逗号
    “com.focus-creative-games.hybridclr_unity”: “https://gitee.com/focus-creative-games/hybridclr_unity.git#v2.0.0-rc”,
    如:
{
    
    
  "dependencies": {
    
    
	"com.focus-creative-games.hybridclr_unity": "https://gitee.com/focus-creative-games/hybridclr_unity.git#v2.0.0-rc",
    "com.unity.2d.sprite": "1.0.0",
    "com.unity.2d.tilemap": "1.0.0",
    "com.unity.ide.rider": "3.0.7",
    "com.unity.ide.visualstudio": "2.0.12",
    "com.unity.render-pipelines.universal": "10.8.1",
    "com.unity.textmeshpro": "3.0.6",
    "com.unity.timeline": "1.6.2",
    "com.unity.ugui": "1.0.0",
    "com.unity.modules.ai": "1.0.0",
    "com.unity.modules.androidjni": "1.0.0",
    "com.unity.modules.animation": "1.0.0",
    "com.unity.modules.assetbundle": "1.0.0",
    "com.unity.modules.audio": "1.0.0",
    "com.unity.modules.cloth": "1.0.0",
    "com.unity.modules.director": "1.0.0",
    "com.unity.modules.imageconversion": "1.0.0",
    "com.unity.modules.imgui": "1.0.0",
    "com.unity.modules.particlesystem": "1.0.0",
    "com.unity.modules.physics": "1.0.0",
    "com.unity.modules.physics2d": "1.0.0",
    "com.unity.modules.screencapture": "1.0.0",
    "com.unity.modules.terrain": "1.0.0",
    "com.unity.modules.terrainphysics": "1.0.0",
    "com.unity.modules.tilemap": "1.0.0",
    "com.unity.modules.ui": "1.0.0",
    "com.unity.modules.uielements": "1.0.0",
    "com.unity.modules.umbra": "1.0.0",
    "com.unity.modules.unityanalytics": "1.0.0",
    "com.unity.modules.unitywebrequest": "1.0.0",
    "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
    "com.unity.modules.unitywebrequestaudio": "1.0.0",
    "com.unity.modules.unitywebrequesttexture": "1.0.0",
    "com.unity.modules.unitywebrequestwww": "1.0.0",
    "com.unity.modules.vehicles": "1.0.0",
    "com.unity.modules.video": "1.0.0",
    "com.unity.modules.wind": "1.0.0"
  }
}

  • 方法二:
    使用 git 拉取 https://gitee.com/focus-creative-games/hybridclr_unity.git 项目到本地
    创建文件夹 com.focus-creative-games.hybridclr_unity
    把拉取下来的文件拷贝到 com.focus-creative-games.hybridclr_unity 文件夹(不要拷贝 .git 文件夹)
    把 com.focus-creative-games.hybridclr_unity 文件夹 拷贝到 Unity项目/Packages/ 文件夹下

安装

运行 HybridCLR/Installer… 再点击安装 (有的版本比较特殊)

HybridCLR Unity项目设置

Build Setting 设置

  1. 使用 Faster runtime。(有的平台有,有的平台无此选项)

Player 设置

  1. Scripting Backend 必须是 IL2CPP
  2. Api Compatibility Level* 没特殊情况 设置为 .Net 4.x
  3. Use incremental GC 取消勾选,暂时不支持增量GC

HybridCLR Settings

  1. 热更新 dll(Hot Update Assemblies) 与 热更新 Assembly Definitions(Hot Update Assembly Definitions)
    不能把同一个dll 名字配置到 两个配置中。 两个都表示可以热更新的 dll 名称(不带扩展名)
    Hot Update Assembiles 表示普通dll
    Hot Update Assembly Definitions 表示Unity项目中的(assembly definition)程序集
  2. externalHotUpdateAssemblyDirs 外部热更新dll搜索路径
    如果热更新 dll 项目 是Unity外部的,则需要配置一个外部dll所在的路径。

泛型

1.HybridCLR的补充元数据技术。如果热更新 dll 中使用到 aot dll 中的泛型类,那么这个aot dll 就需要调用RuntimeApi.LoadMetadataForAOTAssembly 方法来补充元数据。
补充元数据有两个模式
HomologousImageMode.Consistent 只能使用裁减后的AOT dll
HomologousImageMode.SuperSet 既可以使用裁减后的AOT dll 也可以使用原始 AOT dll
补充元数据的泛型函数以解释方式运行,执行效率慢,最好提前在AOT中泛型实例化,(HybridCLR/Generate/AOTGenericReference)工具可以自动收集泛型实例
2.il2cpp 的泛型共享技术 (值类型无法使用,最好还是使用(第一种方法+AOT中编写一个泛型实例化))
详细介绍

我的理解

  1. AOT 表示表示静态编译。上下文中的 AOT dll 表示静态编译的 dll(可以理解为除了热更新dll,其他的就是 AOT dll)。静态编译一般是不能被更新的,HybridCLR 中也可以被热更新(需要使用DHE)
  2. 热更新 可以 依赖 热更新dll 以及 AOT dll, 但是 AOT 不可以依赖 热更新 dll。所以热更新dll一般采用 Assembly-CSharp 或者 外部 dll作为热更新 dll。
  3. 热更新多个 dll 时,需要先加载被依赖的 dll
  4. link.xml 中可以配置一些预留的 保留类。方便以后热更新dll 使用

接入实战

一个基于GameFrarmeWork(GF框架)的游戏框架

环境 Unity 2020.3.26f1

我之前是 2019.4.33f1 但是官方最低只支持 2019.4.40,如果要使用 2019.4.33f1 需要自己手动修改 il2cpp.dll。2019 与 2021 都需要额外处理一些东西,所以我选择了进行Unity升级。2020 版本不需要额外改动,是最简单接入的版本。
我做这个热更新只是为了打包方便,如果出现bug不想再用Unity Build项目。不做网络下载部分。

接入步骤
  1. 在Unity 项目的manifest中新增一行
    “com.focus-creative-games.hybridclr_unity”: “https://gitee.com/focus-creative-games/hybridclr_unity.git#v2.0.0-rc”,
  2. 点击 HybridCLR Installer -> 安装
  3. 修改相关配置
    关闭增量GC 取消 Use incremental GC 勾选
    配置热更新dlls 我选择热更新 Unity 主项目 Assembly-CSharp
  4. 新增游戏入口程序集,创建入口场景,编写入口场景
    在这里插入图片描述
    在这里插入图片描述
    Entry 脚本
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HybridCLR;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

public class Entry : MonoBehaviour
{
    
    
    /// <summary>
    /// 这里填写需要补充元数据的 AOT dll
    /// </summary>
    public static List<string> AOTMetaAssemblyNames {
    
     get; } = new List<string>()
    {
    
    
        "mscorlib.dll",
        "System.dll",
        "System.Core.dll",
        
        //这里填写自己的ATO dll
        "FGUI.dll",
        "LightVelocityFrame.dll",
        "UnityGameFramework.Runtime.dll",
        "UnityEngine.dll",
        "GameFramework.dll",
        "Newtonsoft.Json.dll"
    };

    public const string MainHotFixDll = "Assembly-CSharp.dll";

    public static Assembly MainAssembly
    {
    
    
        get;
        private set;
    }

    //这里填写热更新 dll
    public static List<string> HotFixAssemblyNames {
    
     get; } = new List<string>()
    {
    
    
    };

    public static string DllDir => $"{
      
      Application.streamingAssetsPath}/HotFixDll";

    public static string GetDllPath(string asset)
    {
    
    
        return $"{
      
      DllDir}/{
      
      asset}";
    }

    /// <summary>
    /// 下载好的资源
    /// </summary>
    private static Dictionary<string, byte[]> s_assetDatas = new Dictionary<string, byte[]>();

    public static byte[] GetAssetData(string dllName)
    {
    
    
        return s_assetDatas[dllName];
    }
    
    // Start is called before the first frame update
    void Start()
    {
    
    
        StartCoroutine(DownLoadAssets(this.StartGame));
    }

    private string GetWebRequestPath(string asset)
    {
    
    
        var path = GetDllPath(asset);
        if (!path.Contains("://"))
        {
    
    
            path = "file://" + path;
        }
        if (path.EndsWith(".dll"))
        {
    
    
            path += ".bytes";
        }
        return path;
    }

    public IEnumerator DownLoadAssets(Action onDownloadComplete)
    {
    
    
        var assets = new List<string>(){
    
    MainHotFixDll}.Concat(AOTMetaAssemblyNames).Concat(HotFixAssemblyNames);

        
        foreach (var asset in assets)
        {
    
    
            string dllPath = GetWebRequestPath(asset);
            UnityWebRequest www = UnityWebRequest.Get(dllPath);
            yield return www.SendWebRequest();
            
#if UNITY_2020_1_OR_NEWER
            if (www.result != UnityWebRequest.Result.Success)
            {
    
    
                Debug.Log(www.error);
            }
#else
            if (www.isHttpError || www.isNetworkError)
            {
    
    
                Debug.Log(www.error);
            }
#endif
            else
            {
    
    
                // Or retrieve results as binary data
                byte[] assetData = www.downloadHandler.data;
                Debug.Log($"dll:{
      
      asset}  size:{
      
      assetData.Length}");
                s_assetDatas[asset] = assetData;
            }
        }
        onDownloadComplete();
    }
    
    /// <summary>
    /// 启动游戏
    /// </summary>
    void StartGame()
    {
    
    
        LoadMetadataForAOTAssemblies();

        LoadHotFixDll();

        SceneManager.LoadScene("GameScene");
        
        //执行热更新入口代码 换成自己的
        var method = MainAssembly.GetType("LightVelocityFrame.GameEntry").GetMethod("Init");
        method.Invoke(null, null);
    }

    /// <summary>
    /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。
    /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行
    /// </summary>
    private static void LoadMetadataForAOTAssemblies()
    {
    
    
        /// 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。
        /// 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误
        /// 
        HomologousImageMode mode = HomologousImageMode.SuperSet;
        foreach (var aotDllName in AOTMetaAssemblyNames)
        {
    
    
            byte[] dllBytes = GetAssetData(aotDllName);
            // 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码
            LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode);
            Debug.Log($"LoadMetadataForAOTAssembly:{
      
      aotDllName}. mode:{
      
      mode} ret:{
      
      err}");
        }
    }

    private static void LoadHotFixDll()
    {
    
    
        MainAssembly = System.Reflection.Assembly.Load(GetAssetData(MainHotFixDll));
        
        foreach (var hotFixDllName in HotFixAssemblyNames)
        {
    
    
#if !UNITY_EDITOR
        System.Reflection.Assembly.Load(GetAssetData(hotFixDllName));
#endif
        }
    }
}


  1. 编写编辑器脚本 实现以下功能
    在 HybridCLRData\AssembliesPostIl2CppStrip\Android 文件夹中找出,热更新dll 中可能会继承的类所在的AOT程序集。放入到项目 StreamingAssets/HotFixDll目录中,并把后缀名改为 bytes
    在 HybridCLRData\HotUpdateDlls\Android 文件夹中找出,热更新 dll。放到项目StreamingAssets/HotFixDll目录中,并把后缀名改为 bytes
using System.Collections.Generic;
using System.IO;
using System.Linq;
using HybridCLR.Editor;
using HybridCLR.Editor.Commands;
using UnityEditor;
using UnityEngine;

namespace LightVelocityFrame.Editor
{
    
    
    public static class HybridCLRTool
    {
    
    
        private const string TitleGenerateAll = "【LVFrame.Builder】/HotUpdate_GenerateAll";

        private const string TitleCompileDll = "【LVFrame.Builder】/HotUpdate_Compile";
        
        [MenuItem(TitleGenerateAll)]
        public static void GenerateAndCopyDll()
        {
    
    
            PrebuildCommand.GenerateAll();
            CopyDll2StreamingAssets();
        }

        [MenuItem(TitleCompileDll)]
        public static void CompileDll()
        {
    
    
            CompileDllCommand.CompileDll(EditorUserBuildSettings.activeBuildTarget);
            CopyHotFixAssembilesToStreamingAssets();
        }

        private static void CopyDll2StreamingAssets()
        {
    
    
            CopyAOTAssembliesToStreamingAssets();
            CopyHotFixAssembilesToStreamingAssets();
        }

        private static void CopyAOTAssembliesToStreamingAssets()
        {
    
    
            var target = EditorUserBuildSettings.activeBuildTarget;
            string aotAssembliesSrcDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
            string aotAssembliesDstDir = Entry.DllDir;
            
            foreach (var dll in Entry.AOTMetaAssemblyNames)
            {
    
    
                string srcDllPath = $"{
      
      aotAssembliesSrcDir}/{
      
      dll}";
                if (!File.Exists(srcDllPath))
                {
    
    
                    Debug.LogError($"没有找到 裁减后的 aot-dll {
      
      dll}, 请使用 Generate/all 命令生成 aot dll");
                    continue;
                }
                string dllBytesPath = $"{
      
      aotAssembliesDstDir}/{
      
      dll}.bytes";
                File.Copy(srcDllPath, dllBytesPath, true);
                Debug.Log($"[CopyAOTAssembliesToStreamingAssets] copy AOT dll {
      
      srcDllPath} -> {
      
      dllBytesPath}");
            }
        }

        private static void CopyHotFixAssembilesToStreamingAssets()
        {
    
    
            var target = EditorUserBuildSettings.activeBuildTarget;
            string hotAssembliesSrcDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
            string hotAssembliesDstDir = Entry.DllDir;
            
            var hotFixAssemblyNames = new List<string>(){
    
    Entry.MainHotFixDll}.Concat(Entry.HotFixAssemblyNames);
            foreach (var dll in hotFixAssemblyNames)
            {
    
    
                string srcDllPath = $"{
      
      hotAssembliesSrcDir}/{
      
      dll}";
                if (!File.Exists(srcDllPath))
                {
    
    
                    Debug.LogError($"没有找到热更新dll {
      
      dll}, 请使用 Generate/all 命令生成热更新dll");
                    continue;
                }
                string dllBytesPath = $"{
      
      hotAssembliesDstDir}/{
      
      dll}.bytes";
                File.Copy(srcDllPath, dllBytesPath, true);
                Debug.Log($"[CopyAOTAssembliesToStreamingAssets] copy AOT dll {
      
      srcDllPath} -> {
      
      dllBytesPath}");
            }
        }
    }
}
  1. 使用 HybridCLR/GenerateAndCopyDll 命令生成 并 拷贝 aot dll 以及 热更新 dll
    • 可能遇到的问题 Exception: resolve assembly: xxxx 失败! 如果是热更新dll找不到,请先运行HybridCLR/CompileDll/ActiveBuildTarget编译生成热更新dll。如果是AOT dll找不到,请先运行HybridCLR/Generate/LinkXml,接着在Build Settings中打包或者导出工程来生成AOT dll
      解决方案
      如果 xxxx 是 netstandard,那么是 Api Compatibility Level* 设置问题,需要改成 4.x
      如果 xxxx 是其他dll,这个问题很奇怪,解决方案在AOT dll 中找个地方使用 这个报错dll(定义字段,或者调用方案)。或者找到热更新代码中调用到这个dll的地方,尝试删除,不能删除就用前面这个方法。如果不行,重启再试。(我是这么解决的)。
  2. 构建 Android 项目
  3. 直接使用Android 构建 apk
遇到的问题

由于我的是老项目,不能直接使用 Andorid 构建好的项目。需要把Unity Build的项目与老项目进行融合,导致了一些错误。

  1. JNI DETECTED ERROR IN APPLICATION: JNI GetStaticMethodID called with pending exception java.lang.ClassNotFoundException: Didn’t find class “com.unity3d.player.PlayAssetDeliveryUnityWrapper”
    出现这个错误是 unity-classes.jar 这个文件没有同步,升级了Unity版本的原因。

  2. JNI FatalError called: Unable to load library:xxxxx [dlopen failed: library “libil2cpp.so” not found]
    build.gradle 中需要加入 BuildIL2Cpp 任务,参考Unity Build 的安卓项目

  3. 安卓 Build 报错 NDK is not installed
    在local.properties 中配置 ndk 目录
    在这里插入图片描述

  4. Script Missing GF 使用HybridCLR 很可能会遇上这个错误
    错误原因:热更MonoBehaviour,如果是挂载在Prefab上,那么这个Prefab 需要是AssetBundle
    解决方法:参考
    使用AddComponent 添加 或者 把Prefab打包成AssetBundle
    GF 解决思路:
    GF 的基础组件作为 AOT 程序集,
    GF 的自定义组件使用 AddComponent 添加到游戏物体上

  5. 遇到 MissingMethodException xxx 错误
    错误原因:热更新中使用了其他程序集的泛型类,而没有把这个程序集作为AOT dll 添加注册元数据。
    解决方法:把这个Dll 配置到Entry.AOTMetaAssemblyNames 字段中
    调优,把这个泛型类 在 AOT dll中定义一次

猜你喜欢

转载自blog.csdn.net/qq_27461747/article/details/128939853