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
}