UH4L2——Unity中启动ILRuntime

本章代码关键字

1
2
3
4
5
AppDomain                                //用于解释执行dll和pdb文件的类
appDomain.LoadAssembly() //传入dll和pdb的流,读取热更内容
PdbReaderProvider //读取Pdb相关内容,使用appDomain.LoadAssembly()读取pdb时需要实例化该类并传入
appDomain.UnityMainThreadID //向AppDomain设置Unity的主线程的线程ID,以便于让ILRuntime相关可以在Unity的Profiler窗口内查看
Thread.CurrentThread.ManagedThreadId //当前托管线程的主线程

ILRuntime关键类AppDomain

ILRuntime 的开发方式,是在 Unity 主工程和 ILRuntime 热更工程中进行开发的,两个工程之间可以相互访问调用
ILRuntime 热更代码最终会生成一个 dll 文件和一个 pdb 文件,这里面就包含了我们热更代码的相关信息
而 ILRuntime 提供了AppDomain​类,是ILRuntime提供的用于解释执行dll和pdb文件的,通过它我们才能解释执行我们的热更代码
它的作用就有点类似xLua中的 LuaEnv​ lua解析器

从本节Unity中启动 ILRuntime 我们更能够感受到,ILRuntime 热更新的内容其实就是热更工程中的dll文件和pdb文件
这两个文件中就包含了我们所有的热更 C# 代码信息,以后如果我们要做远端热更新,只需要把他们放入AB包下载即可

Unity中启动ILRuntime

  1. 声明 AppDomain​ 对象(命名空间 ILRuntime.Runtime.Enviorment​)

    1
    2
    3
    4
    5
    6
    private AppDomain appDomain;

    void Start()
    {
    appDomain = new AppDomain();
    }
  2. 加载本地或远端下载的dll和pdb文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class Lesson2 : MonoBehaviour
    {
    private AppDomain appDomain;

    void Start()
    {
    appDomain = new AppDomain();
    StartCoroutine(LoadHotUpdateInfo());
    }

    /// <summary>
    /// 去异步加载我们的热更新相关的dll和pdb文件
    /// </summary>
    /// <returns></returns>
    IEnumerator LoadHotUpdateInfo()
    {
    //异步加载DLL文件
    #if UNITY_ANDROID
    UnityWebRequest reqDll = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.dll");
    #else
    UnityWebRequest reqDll = UnityWebRequest.Get("file://" + Application.streamingAssetsPath + "/HotFix_Project.dll");
    #endif
    yield return reqDll.SendWebRequest();
    if (reqDll.result != UnityWebRequest.Result.Success)
    print("加载DLL文件失败" + reqDll.responseCode + reqDll.result);
    //读取加载的DLL数据
    byte[] dll = reqDll.downloadHandler.data;
    reqDll.Dispose();
    //我们还可以加载调试用的pdb文件
    UnityWebRequest reqpdb = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.pdb");
    yield return reqpdb.SendWebRequest();
    if (reqpdb.result != UnityWebRequest.Result.Success)
    print("加载DLL文件失败" + reqpdb.responseCode + reqpdb.result);
    //读取加载的DLL数据
    byte[] pdb = reqpdb.downloadHandler.data;
    reqpdb.Dispose();
    }
    }
  3. 将加载的数据以流的形式(文件流或者内存流对象)传递给AppDomain​对象中的LoadAssembly​方法

  4. 初始化ILRuntime相关信息(目前只需要告诉ILRuntimeUnity的主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    using ILRuntime.Mono.Cecil.Pdb;
    using ILRuntime.Runtime.Enviorment;
    using System.Collections;
    using System.IO;
    using System.Threading;
    using UnityEngine;
    using UnityEngine.Networking;

    public class Lesson2 : MonoBehaviour
    {
    private AppDomain appDomain;
    //用于存储加载出来的两个文件的内存流对象
    private MemoryStream dllStream;
    private MemoryStream pdbStream;

    // Start is called before the first frame update
    void Start()
    {
    appDomain = new AppDomain();
    StartCoroutine(LoadHotUpdateInfo());
    }

    /// <summary>
    /// 去异步加载我们的热更新相关的dll和pdb文件
    /// </summary>
    /// <returns></returns>
    IEnumerator LoadHotUpdateInfo()
    {
    //异步加载DLL文件
    #if UNITY_ANDROID
    UnityWebRequest reqDll = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.dll");
    #else
    UnityWebRequest reqDll = UnityWebRequest.Get("file://" + Application.streamingAssetsPath + "/HotFix_Project.dll");
    #endif
    yield return reqDll.SendWebRequest();
    if (reqDll.result != UnityWebRequest.Result.Success)
    print("加载DLL文件失败" + reqDll.responseCode + reqDll.result);
    //读取加载的DLL数据
    byte[] dll = reqDll.downloadHandler.data;
    reqDll.Dispose();
    //编辑器环境下,我们可以加载调试用的pdb文件
    UnityWebRequest reqpdb = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.pdb");
    yield return reqpdb.SendWebRequest();
    if (reqpdb.result != UnityWebRequest.Result.Success)
    print("加载DLL文件失败" + reqpdb.responseCode + reqpdb.result);
    //读取加载的DLL数据
    byte[] pdb = reqpdb.downloadHandler.data;
    reqpdb.Dispose();
    pdbStream = new MemoryStream(pdb);
    dllStream = new MemoryStream(dll);
    //将我们两个文件的内存流用于初始化 appDomain 我们之后就可以通过该对象来执行我们对应的热更代码了
    appDomain.LoadAssembly(dllStream, pdbStream, new PdbReaderProvider());
    //初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
    appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
    }
    }
  5. 执行热更代码中的逻辑

注意:一般在一个项目中,大多数情况下只需要一个 AppDomain​ 对象

关于ILRuntime相关内容的释放

注意!为 AppDomain​ 读取 dll 文件和 pdb 文件而创建的两个内存流不可以随意关闭,只有在热更相关内容不再使用时才可用关闭

1
2
3
4
5
6
7
8
9
10
private void OnDestroy()
{
if (dllStream != null)
dllStream.Dispose();
if (pdbStream != null)
pdbStream = null;
dllStream = null;
pdbStream = null;
appDomain = null;
}

声明一个管理ILRuntime相关的管理器

  • 由于会使用协程相关内容加载dll和pdb文件,因此该管理器是继承 MonoBehaviour​ 的
  • 为了外部可以使用ILRuntime相关内容,因此管理器的 AppDomain​ 是公开的,供外部调用
  • StartILRuntime​:调用该方法会异步加载dll文件和pdb文件,然后将其作为内存流由 AppDomain​ 读取,异步执行完毕后会执行传入的回调方法
  • StopILRuntime​:会释放两个内存流和 AppDomain​ 对象
  • InitILRuntime​:初始化 AppDomain​ 的方法,在AppDomain​对象读取了两个流之后就会调用
  • ILRuntimeLoadOverDo​:在加载完 dll 文件和 pdb 文件并初始化 AppDomain​ 后,会执行的方法
  • LoadHotUpdateInfo​:异步加载 dll 文件和 pdb 文件并执行初始化方法的协程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
using ILRuntime.Mono.Cecil.Pdb;
using ILRuntime.Runtime.Enviorment;
using System.Collections;
using System.IO;
using System.Threading;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

public class ILRuntimeMgr : MonoBehaviour
{
private static ILRuntimeMgr instance;
public static ILRuntimeMgr Instance
{
get
{
if (instance == null)
{
GameObject obj = new GameObject("Manager_ILRuntime");
instance = obj.AddComponent<ILRuntimeMgr>();
DontDestroyOnLoad(obj);
}
return instance;
}
}

private MemoryStream dllStream;
private MemoryStream pdbStream;
public AppDomain appDomain;
private bool isStart = false;

/// <summary>
/// 启动ILRuntime的初始化方法
/// </summary>
public void StartILRuntime(UnityAction callBack = null)
{
if (!isStart)
{
appDomain = new AppDomain();
StartCoroutine(LoadHotUpdateInfo(callBack));
isStart = true;
}
}

public void StopILRuntime()
{
if (dllStream != null)
dllStream.Dispose();
if (pdbStream != null)
pdbStream = null;
dllStream = null;
pdbStream = null;
appDomain = null;
}

//初始化ILRuntime相关的方法
private void InitILRuntime()
{
//其他方法
//初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
}

//ILRuntime初始化完毕时会执行的方法
private void ILRuntimeLoadOverDo()
{

}

private IEnumerator LoadHotUpdateInfo(UnityAction callBack)
{
//异步加载DLL文件
#if UNITY_ANDROID
UnityWebRequest reqDll = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.dll");
#else
UnityWebRequest reqDll = UnityWebRequest.Get("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endif
yield return reqDll.SendWebRequest();
if (reqDll.result != UnityWebRequest.Result.Success)
Debug.Log("加载DLL文件失败" + reqDll.responseCode + reqDll.result);
//读取加载的DLL数据
byte[] dll = reqDll.downloadHandler.data;
reqDll.Dispose();
//编辑器环境下,我们可以加载调试用的pdb文件
UnityWebRequest reqpdb = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.pdb");
yield return reqpdb.SendWebRequest();
//如果加载失败
if (reqpdb.result != UnityWebRequest.Result.Success)
{
Debug.LogError("加载DLL文件失败" + reqpdb.responseCode + reqpdb.result);
StopILRuntime();
yield break;
}
//读取加载的DLL数据
byte[] pdb = reqpdb.downloadHandler.data;
reqpdb.Dispose();
pdbStream = new MemoryStream(pdb);
dllStream = new MemoryStream(dll);
//将我们两个文件的内存流用于初始化 appDomain 我们之后就可以通过该对象来执行我们对应的热更代码了
appDomain.LoadAssembly(dllStream, pdbStream, new PdbReaderProvider());
InitILRuntime();
ILRuntimeLoadOverDo();
callBack?.Invoke();
}
}