Unity ILRumtime 學習筆記

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
    }

更新到這裏吧…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章