ILRuntime Unity 热更新框架学习记录。
首先需要将与 ILRuntime 相关的框架代码拷贝到一个新创建的Unity项目工程中,以保证Unity可以通过C#与框架进行通信交互。
下边是需要使用到的框架源文件所在文件夹名称,可以借鉴参考
ILRuntime
LitJson(非必须,一个可以用来操作Json数据文件的框架)
Mono.Cecil.20
Mono.Cecil.Pdb
其次需要创建一个存放用于热更新的 dll文件 和 pdb 文件的路径。
注意:这个文件夹必须在项目的顶级目录创建(例如:ILRuntime_Projects),而不是项目的资源目录(Assets)
例如: Hotfix
热更新的文件必须单独建立一个文件夹,并存放在项目的资源文件目录下(Assets)。
例如 ILTest
文件夹下必须创建一个 Assembly 文件,这个是用来存储程序集相关性的信息。
UnityTools -> 工具类
ILManager -> 与热更新分支工程相关的业务代码编写
目前ILManager需要修改的地方只需要一处,前面的基础业务调用已经抽象出来,只需要根据需要修改设置对应的程序集名称和类型名称即可。
// Unity 辅助工具类
public class UnityTools
{
// 获取项目文件中的父级目录(这里也就是为了查找热更新所需要使用到的 dll 文件所在路径)
public static string GetProjectParentPath(string dirName, SearchOption searchOption)
{
string output = "";
if (Directory.Exists(Application.dataPath))
{
DirectoryInfo dirInfo = Directory.GetParent(Application.dataPath);
DirectoryInfo[] dirInfos = dirInfo.GetDirectories(dirName, searchOption);
if (dirInfos != null && dirInfos.Length > 0)
output = dirInfos[0].FullName;
}
return output;
}
}
/// <summary>
/// ILRuntime 管理类(负责与 热更新工程进行通信处理)
/// </summary>
public class ILManager : MonoBehaviour
{
// 这个不是System中的AppDomain
// 是ILRuntime 框架下的AppDomain
private AppDomain mAppDomain;
// DataFilePath ->热更新需要使用到的 dll 和 pdb 文件路径
// DataFile -> 文件名称
// DllDataFile -> DataFilePath + DataFile + '.dll'
// PdbDataFile -> DataFilePath + DataFile + '.pdb'
private static string DataFilePath, DataFile, DllDataFile, PdbDataFile;
// 从 dll 和 pdb 文件中加载出来的二进制数据
private byte[] mDllBytes, mPdbBytes;// DLL,PDB 数据文件所在
#region 设置数据文件加载信息
// 设置热更新 dll 和 pdb 文件所在的路径,例如 'Hotfix'
// @param [dataFilePath] 文件路径
public void SetDataFilePath(string dataFilePath)
{
if (!string.IsNullOrEmpty(dataFilePath))
DataFilePath = dataFilePath;
}
public void SetDataFile(string dataFileName)
{
if (!string.IsNullOrEmpty(dataFileName))
DataFile = dataFileName;
if (!string.IsNullOrEmpty(DataFile))
{
DllDataFile = Path.Combine(DataFilePath, DataFile + ".dll");
PdbDataFile = Path.Combine(DataFilePath, DataFile + ".pdb");
}
}
private void LoadDataFile(string path, string file)
{
// 开启一个协同函数,加载 dll 程序集文件
this.StartCoroutine(this.LoadHotfixAssembly());
}
#endregion
#region Unity 消息事件
private void Awake()
{
this.SetDataFilePath(UnityTools.GetProjectParentPath("Hotfix", SearchOption.AllDirectories));
this.SetDataFile("ILTest");
}
private void Start()
{
// 测试加载
this.LoadDataFile(UnityTools.GetProjectParentPath("Hotfix", SearchOption.AllDirectories), "ILTest");
//this.LoadDataFile(UnityTools.GetProjectParentPath("Hotfix", SearchOption.AllDirectories), "ILTest");
}
#endregion
#region 使用 WWW 加载 DLL 和 PDB 文件
/// <summary>
/// 加载DLL程序集文件
/// </summary>
/// <returns></returns>
private IEnumerator LoadDllDataFile()
{
WWW www = null;
#if UNITY_ANDROID
www = new WWW(DllDataFile);
#else
www = new WWW(DllDataFile);
#endif
// 等待 WWW 加载程序集完成
while (!www.isDone)
yield return null;
// 如果有错误,就表示.dll文件加载失败了
if (!string.IsNullOrEmpty(www.error))
Debug.Log(www.error);
mDllBytes = www.bytes;
www.Dispose();
}
/// <summary>
/// 加载PDB数据文件
/// </summary>
/// <returns></returns>
private IEnumerator LoadPdbDataFile()
{
WWW www = null;
#if UNTIY_ANDROID
www = new WWW(PdbDataFile);
#else
www = new WWW(PdbDataFile);
#endif
// 等待 WWW 加载pdb(主数据文件)完成
while (!www.isDone)
yield return null;
// 如果有错误,就表示.dll文件加载失败了
if (!string.IsNullOrEmpty(www.error))
Debug.Log(www.error);
mPdbBytes = www.bytes;
using (MemoryStream msDll = new MemoryStream(mDllBytes))
{
using (MemoryStream msPdb = new MemoryStream(mPdbBytes))
this.mAppDomain.LoadAssembly(msDll, msPdb, new PdbReaderProvider());
}
}
#endregion
/// <summary>
/// 加载热更新程序集文件
/// </summary>
/// <returns></returns>
private IEnumerator LoadHotfixAssembly()
{
// 构建应用程序域(沙盒环境)
this.mAppDomain = new AppDomain();
if (Directory.Exists(DataFilePath))
{
// 开启新协同函数,等待加载DLL数据文件
yield return this.StartCoroutine(this.LoadDllDataFile());
// 开启新协同函数,等待加载PDB数据文件
yield return this.StartCoroutine(this.LoadPdbDataFile());
this.InitalizeILRuntime();
this.OnHotfixLoaded();
}
else
Debug.Log("Hotfix 文件目录不存在");
yield return new WaitForSeconds(2.0f);
Debug.Log("协同函数执行完毕");
}
/// <summary>
/// 初始化IL运行时
/// </summary>
private void InitalizeILRuntime()
{
Debug.Log("InitalizeILRuntime");
if (this.mAppDomain != null)
{
// 这里是进行跨应用程序域访问委托类型的处理,两个不同的程序集
// 在 ILRuntime 框架运行时需要调用委托所指向的函数,必须先为其注册对应的委托
// 注册不同委托类型所需要的方法
// 注册 Action 委托类型所需要的方法
this.mAppDomain.DelegateManager.RegisterMethodDelegate<string>();
// 注册 Func 委托类型所需要的方法
this.mAppDomain.DelegateManager.RegisterFunctionDelegate<int, int, string>();
this.mAppDomain.DelegateManager.RegisterFunctionDelegate<int, int, int, string>();
// 构建委托转换器(注册一个委托转换器类型)
// 这个是与 (Unity) 的事件 (Events) 类交互时候需要的类似于注册时间的处理操作
// 返回一个 (UnityAction) 类型的对象,返回的同时,通过 (Action) 类型强转进行回调函数 [action()] 的调用
this.mAppDomain.DelegateManager.RegisterDelegateConvertor<UnityAction>(
(action) => { return new UnityAction(() => { ((System.Action)action)(); }); });
this.mAppDomain.DelegateManager.RegisterDelegateConvertor<UnityAction<string>>(
(action) => { return new UnityAction<string>((msg) => { ((System.Action<string>)action)(msg); }); });
// 注册适配器类型
this.mAppDomain.RegisterCrossBindingAdaptor(new UIBaseAdaptor());
}
}
/// <summary>
/// 加载热更新(调用程序集中的类)
/// </summary>
private void OnHotfixLoaded()
{
Debug.Log("OnHotfixLoaded");
if (this.mAppDomain != null)
{
// Invoke(类型的完全限定名,函数名称,(如果是实例函数,就传入对应的实例对象,否则是静态函数,就不需要传递),函数的参数列表)
// 如果函数调用成功,表示测试完成
// 这里只需要根据自己的需要修改所要与之交互的 程序集名称 和 类型名称就可以了
string mAssemblyName = "GEC.Game.Codes.Tests";// 命名空间名称
//string mTypeName = "Test";// 类型名称
string mTypeName = "LoginUI";// 类型名称
string fullName = $"{mAssemblyName}.{mTypeName}";
//object obj = this.mAppDomain.Instantiate(fullName, null);
#region 测试静态函数
//this.TestFunc_01(fullName);
//this.TestFunc_02(fullName);
//this.TestFunc_03(fullName);
#endregion
#region 测试创建对象
//object obj = this.mAppDomain.Instantiate(fullName, null);// 创建对象
#region 第一种方式
//this.mAppDomain.Instantiate(fullName, null);
//this.mAppDomain.Instantiate(fullName, new object[] { "hello world" });
#endregion
#region 第二种方式
//IType type = this.mAppDomain.LoadedTypes[fullName];
//object o = ((ILType)type).Instantiate(false);// 不调用默认的构造函数
//IType strCtorType = this.mAppDomain.GetType(typeof(string));
//List<IType> parameters = new List<IType>
//{
// strCtorType
//};
//IMethod method = type.GetConstructor(parameters);
//this.mAppDomain.Invoke(method, o, new object[] { "hello world" });
#endregion
#region 调用属性和实例函数
//object obj = this.mAppDomain.Instantiate(fullName, null);// 创建实例对象
//var type = this.mAppDomain.LoadedTypes[fullName].ReflectionType;// 获取反射后的类型
//var myId = type.GetProperty("MyId");// 获取属性
//var myName = type.GetProperty("MyName");// 获取属性
//Debug.Log(myId.GetValue(obj, null));
//Debug.Log(myName.GetValue(obj, null));
//Debug.Log(this.mAppDomain.Invoke(fullName, "GetMyName", obj, new object[] { }));
#endregion
#region 测试泛型函数
#region 第一种方式
//IType type = this.mAppDomain.GetType(typeof(string));// 获取指定类型的函数
//IType[] genericArgs = new IType[] { type };// 构建泛型参数类型数组
//this.mAppDomain.InvokeGenericMethod(fullName, "TestGeneric_Func", genericArgs, obj, "TestStr");// 调用泛型函数
#endregion
#region 第二种方式
// 创建泛型参数列表
//List<IType> types = new List<IType>();//
//IType intType = this.mAppDomain.GetType(typeof(int));//
//types.Add(intType);
//IType[] genericArgs = new IType[] { intType };
//// 预先获取IMethod对象,
//IType type = this.mAppDomain.LoadedTypes[fullName];// 是获取对应的类
//IMethod method = type.GetMethod("TestGeneric_Func", types, genericArgs);// 获取对应的泛型方法
//this.mAppDomain.Invoke(method, obj, 23333);// 调用方法
#endregion
#endregion
#endregion
#region 与 MonoBehaviour进行通信
//this.mAppDomain.Invoke(fullName, "Init", obj, null);
#endregion
#region 测试类型适配器
// 测试跨域继承
UIBase mBase = this.mAppDomain.Instantiate<UIBase>(fullName);
mBase.Open("测试调用");
mBase.HandleEvent(100);
Debug.Log("MyId:" + mBase.MyId);
#endregion
}
}
#region 测试调用静态成员
// 第一种调用静态函数的方式
private void TestFunc_01(string fullName)
{
// 调用静态函数测试
//this.mAppDomain.Invoke(fullName, "Initialize", null, null);
//this.mAppDomain.Invoke(fullName, "PlusNumber", null, new object[] { 1, 2 });
// 调用静态函数测试
//this.mAppDomain.Invoke(fullName, "Create", null, null);
//this.mAppDomain.Invoke(fullName, "Create", null, "TestCube");
//this.mAppDomain.Invoke(fullName, "Create", null, new object[] { "TestCube", 5 });
}
// 第二种调用静态函数的方式
private void TestFunc_02(string fullName)
{
IType type = this.mAppDomain.LoadedTypes[fullName];// 获取要调用方法所在的类
IMethod method = type.GetMethod("Create", 2);// 获取方法
this.mAppDomain.Invoke(method, null, new object[] { "TestCube", 5 });// 调用方法
}
// 第三种调用静态函数的方式
private void TestFunc_03(string fullName)
{
// 构建一个参数列表
List<IType> parameters = new List<IType>();
// 添加参数类型
parameters.AddRange(
new List<IType> {
this.mAppDomain.GetType(typeof(string)),
this.mAppDomain.GetType(typeof(int))
});
// 加载类
IType type = this.mAppDomain.LoadedTypes[fullName];
// 获取方法
IMethod method = type.GetMethod("Create", parameters, null);
// 调用方法
this.mAppDomain.Invoke(method, null, new object[] { "TestCube", 5 });
}
#endregion
}