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
    }

更新到这里吧…

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