Unity C#热更新方案 ILRuntime学习笔记(一) Hello World

转载请标明原文地址:https://segmentfault.com/a/11...

一、什么是ILRuntime

问:什么是ILRuntime?
答:一个C#热更新方案。

问:什么是热更新?
答:在不重新下载安装应用包的情况下更新应用内容的方式就叫做热更新。

问:为什么要热更新?
答:为了更好的用户体验。

问:那热更新和非热更新有什么不同吗?
答:用户使用更方便,开发更麻烦。

问:用户方便在哪?开发麻烦在哪?
答:用户方便在当应用更新时,打开应用走个进度条就能用,不用重新下载安装,省心、省时、省流量。
开发麻烦在开发前期的框架搭建,开发中的细节,发开后的测试都增加了一定难度。

问:产生这些麻烦的原因是什么?
答:主要原因在苹果平台,用Unity做移动端应用,通常兼容两个常见平台:安卓和苹果。苹果为了用户的安全,对开发者的应用做了限制,不允许开发者用常规方式更新应用的代码(JIT)。简单来说,就是其他平台都没太大问题,可以很容易的热更新,而苹果的限制让我们必须用其他方法做热更新。现在主流的热更新方案都是因为可以绕过苹果的限制才流行起来的。就像是你出国吃饭的时候饭店不允许你用筷子,因为他们是西餐厅,只提供了刀叉,但餐具费很贵。你既用不惯刀叉又不想花冤枉钱,但是没办法,你在国外,你只能入乡随俗。好在饭店没明确规定不可以自带餐具,于是你带了一种不是筷子但能吃饭的餐具,饭店也就睁一只眼闭一只眼了。这饭店就相当于苹果,餐具就相当于热更新方案。

问:热更新这么麻烦啊,那怎样解决这个问题呢?
答:想要从根本解决这个问题,要么别去苹果饭店吃饭(那就没这个问题了),要么苹果饭店解除限制(不太可能),要么苹果饭店转行了(huang le)。
因为从根本上难以解决该问题,所以现在都是折中的办法,其实是没办法的办法,你要去苹果饭店吃饭就要遵守苹果饭店的规矩。
有些餐具在吃饭时遇到的问题比较少,吃饭比较顺利。有些餐具在吃饭时遇到的问题比较多,吃饭不太顺利。于是问题较少的就逐渐流行开来,问题较多的就逐渐没落,现在主流的热更新方案是Lua,ILRuntime应该算是非主流,因为他比较小众,没流行起来。

问:那为什么不用主流的lua,要用非主流的ILRuntime?
答:因为lua在Unity中用起来很难受,不是lua这门语言不好,而是因为在Unity中官方的开发语言是C#。用lua就意味着开发者要会两种语言,学习和开发成本都高,而且因为C#是强类型、面向对象的语言。lua是弱类型,非面向对象的语言。
lua从编程思想和代码写法都和C#有较大差距,这一点在面对越大的项目时感受越明显,项目小的时候觉得lua还好,项目做大了以后会发现lua带给你的麻烦会大于便利。
而ILRuntime方案是基于C#的,开发语言统一,编码更容易。不过他的缺点是实际经过验证的项目还是太少了,不太成熟,可能有很多坑需要填,不像是经过很多项目验证的lua,有比较成熟的方案。

个人建议:
如果你用lua更熟练,更喜欢lua,就用lua,有很多成熟框架可以用。
如果你用C#更熟练,更喜欢C#,用lua感觉很难受,可以尝试学习使用一下ILRuntime,用熟练了以后再考虑用该方案作为热更新方案。
存在即是合理,lua流行有其优势,ILRuntime出现也有其道理,lua是第一选择,但不是唯一的选择,ILRuntime写代码更舒服,但前方也许有不少坑。

二、下载ILRuntime

GitHub:https://github.com/Ourpalm/IL...
Unity Demo:https://github.com/Ourpalm/IL...
国内码云:https://gitee.com/zhangyu800/...
中文手册:http://ourpalm.github.io/ILRu...

先去GitHub上点个赞,支持一下该项目,再去Unity Demo上把项目下载下来,顺便也点个赞。
如果国外地址下载慢,可以在国内码云上下载Demo。

Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第1张图片

三、导入ILRuntime

1.解压缩Unity Demo
打开Unity工程目录下的ProjectSettings/ProjectVersion.txt 查看工程版本。

为了避免因为不同版本导致的兼容问题,工程版本和Unity版本尽量保持一致,我下载的Demo工程版本是2019.3.6f1, 我尽量用2019.3.6 或稍微高一点儿的版本导入。

Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第2张图片

2.目录结构

工程导入完毕,看下目录结构。

Demo目录:
在Samples/ILRuntime/1.6.2/Demo/_Scenes/Examples文件夹下

Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第3张图片

热更新加载的代码目录:
热更新代码会从StreamingAssets目录下加载编译后的dll
其中mdb和pdb文件都是调试时用的,发布时只需要dll。

Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第4张图片

运行Hello World Demo:

打开并运行 01_Hello World 场景。可以看到控制台输出了如下结果。
Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第5张图片

ILR是如何工作的呢?看下HelloWorld脚本。

using UnityEngine;
using System.Collections;
using System.IO;
using ILRuntime.Runtime.Enviorment;

public class HelloWorld : MonoBehaviour
{
    //AppDomain是ILRuntime的入口,最好是在一个单例类中保存,整个游戏全局就一个,这里为了示例方便,每个例子里面都单独做了一个
    //大家在正式项目中请全局只创建一个AppDomain
    AppDomain appdomain;

    System.IO.MemoryStream fs;
    System.IO.MemoryStream p;
    void Start()
    {
        StartCoroutine(LoadHotFixAssembly());
    }

    IEnumerator LoadHotFixAssembly()
    {
        //首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒
        appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();
        //正常项目中应该是自行从其他地方下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示方便直接从StreammingAssets中读取,
        //正式发布的时候需要大家自行从其他地方读取dll

        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
        //工程目录在Assets\Samples\ILRuntime\1.6\Demo\HotFix_Project~
        //以下加载写法只为演示,并没有处理在编辑器切换到Android平台的读取,需要自行修改
#if UNITY_ANDROID
        WWW www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.dll");
#else
        WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endif
        while (!www.isDone)
            yield return null;
        if (!string.IsNullOrEmpty(www.error))
            UnityEngine.Debug.LogError(www.error);
        byte[] dll = www.bytes;
        www.Dispose();

        //PDB文件是调试数据库,如需要在日志中显示报错的行号,则必须提供PDB文件,不过由于会额外耗用内存,正式发布时请将PDB去掉,下面LoadAssembly的时候pdb传null即可
#if UNITY_ANDROID
        www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#else
        www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endif
        while (!www.isDone)
            yield return null;
        if (!string.IsNullOrEmpty(www.error))
            UnityEngine.Debug.LogError(www.error);
        byte[] pdb = www.bytes;
        fs = new MemoryStream(dll);
        p = new MemoryStream(pdb);
        try
        {
            appdomain.LoadAssembly(fs, p, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
        }
        catch
        {
            Debug.LogError("加载热更DLL失败,请确保已经通过VS打开Assets/Samples/ILRuntime/1.6/Demo/HotFix_Project/HotFix_Project.sln编译过热更DLL");
        }

        InitializeILRuntime();
        OnHotFixLoaded();
    }

    void InitializeILRuntime()
    {
#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)
        //由于Unity的Profiler接口只允许在主线程使用,为了避免出异常,需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler
        appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
        //这里做一些ILRuntime的注册,HelloWorld示例暂时没有需要注册的
    }

    void OnHotFixLoaded()
    {
        //HelloWorld,第一次方法调用
        appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);

    }

    private void OnDestroy()
    {
        if (fs != null)
            fs.Close();
        if (p != null)
            p.Close();
        fs = null;
        p = null;
    }

    void Update()
    {

    }
}

惊喜!注释是纯中文的,作者一定是中国人!注释很详细,看注释就行了。

其中比较重要的代码是:

//这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
//工程目录在Assets\Samples\ILRuntime\1.6\Demo\HotFix_Project~
//以下加载写法只为演示,并没有处理在编辑器切换到Android平台的读取,需要自行修改
WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");

//HelloWorld,第一次方法调用
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);

打开HotFix_Project:
HotFix_Project 是Demo自带的C#热更新代码工程。

工程目录在
Assets\Samples\ILRuntime\1.6\Demo\HotFix_Project~文件夹下,用Visual Studio打开该工程。

注意:HotFix_Project~ 后面带了个波浪号,Unity会自动忽略该目录,资源不会被导入,代码不会被编译,文件不会带进发布包。
具体规则可以看官方文档:
https://docs.unity3d.com/Manu...

修改热更新代码:
打开HotFix项目下的InstanceClass类 修改StaticFunTest方法中输出的内容:

public static void StaticFunTest()
{
    UnityEngine.Debug.Log("调用了热更新类的静态方法!");
}

生成热更新代码dll:
右键项目 > 生成
Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第6张图片

重新运行Hello World Demo:
查看输出结果,可以看到 我们修改的内容被输出了。

Log.png

通过以上步骤,了解了IRuntime的基本运行流程。
1.热更代码生成dll
2.Unity加载dll
3.Unity调用dll里的方法

三、创建自己的Hotfix工程

从零开始创建自己的热更工程,体验完整配置流程。
1.打开Visual Studio,新建项目。
Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第7张图片

2.修改项目配置
选择类库(.NET Framework)。
修改名称为 Hotfix
Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第8张图片

修改路径为 Unity项目根目录
Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第9张图片

3.添加UnityEngine的引用
在Unity中用Visual Studio打开Unity Demo的任意一个脚本,VS会自动关联需要的Unity类库引用,在项目的引用中可以看到引用的dll路径。
Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第10张图片

在我们自己的Hotfix工程引用中添加该文件。

有两个主要的路径

(1)在Unity安装目录下的Editor\Data
我的电脑路径是:D:\SDK\Unity\2019.4.1f1\Editor\Data\Managed\UnityEngine\
把该文件夹内所有dll文件都引入到Hotfix工程里。
Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第11张图片

(2)在Unity工程根目录下的Library\ScriptAssemblies\
我的电脑路径是:
D:\Projects\Unity3D\Unity 2019.3 Projects\ILRuntimeU3D\ILRuntimeDemo\Library\ScriptAssemblies
添加自己需要的dll,我引用了以下两个dll。
Assembly-CSharp.dll
UnityEngine.UI.dll

注:Unity 2019中已经把类库拆的很零散了,应该是为了解耦,为了以后的规划。他把类库拆成两个部分,内置的类库放在Unity安装目录下了,PackageManager包管理器下载的插件都放在Unity工程目录下了,UGUI的库从内置位置移动到PackageManager里了,可能以后要淘汰掉。

4.编写 Hello World
添加完引用,可以开始写代码了。
在我们自己的Hotfix工程中添加一个类 HelloWorld.cs 并输入以下代码:

namespace Hotfix {

    using UnityEngine;

    // 冰封百度的Blog:https://segmentfault.com/a/1190000023183723
    public class HelloWorld {

        public void Test() {
            Debug.Log("Hello World");
        }

    }

}

5.配置Hotfix.dll输出路径
右键Hotfix工程, 选择生成选项卡,在下方的输出路径里浏览到StreamingAssets文件夹或填写相对路径:..\..\Assets\StreamingAssets\
Unity C#热更新方案 ILRuntime学习笔记(一) Hello World_第12张图片

5.生成Hotfix.dll
右键Hotfix工程,选择生成。
生成成功后可以看到如下提示,如果失败则根据错误提示进行处理。
Hotfix 生成成功.png

6.修改Unity Demo中的HelloWorld脚本
主要修改脚本中加载dll的名称,HotFix_Project改为Hotfix。
并且添加调用热更方法的代码。

修改后如下:

using UnityEngine;
using System.Collections;
using System.IO;
using ILRuntime.Runtime.Enviorment;

public class HelloWorld : MonoBehaviour
{
    //AppDomain是ILRuntime的入口,最好是在一个单例类中保存,整个游戏全局就一个,这里为了示例方便,每个例子里面都单独做了一个
    //大家在正式项目中请全局只创建一个AppDomain
    AppDomain appdomain;

    System.IO.MemoryStream fs;
    System.IO.MemoryStream p;
    void Start()
    {
        StartCoroutine(LoadHotFixAssembly());
    }

    IEnumerator LoadHotFixAssembly()
    {
        //首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒
        appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();
        //正常项目中应该是自行从其他地方下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示方便直接从StreammingAssets中读取,
        //正式发布的时候需要大家自行从其他地方读取dll

        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
        //工程目录在Assets\Samples\ILRuntime\1.6\Demo\HotFix_Project~
        //以下加载写法只为演示,并没有处理在编辑器切换到Android平台的读取,需要自行修改
#if UNITY_ANDROID
        WWW www = new WWW(Application.streamingAssetsPath + "/Hotfix.dll");
#else
        WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/Hotfix.dll");
#endif
        while (!www.isDone)
            yield return null;
        if (!string.IsNullOrEmpty(www.error))
            UnityEngine.Debug.LogError(www.error);
        byte[] dll = www.bytes;
        www.Dispose();

        //PDB文件是调试数据库,如需要在日志中显示报错的行号,则必须提供PDB文件,不过由于会额外耗用内存,正式发布时请将PDB去掉,下面LoadAssembly的时候pdb传null即可
#if UNITY_ANDROID
        www = new WWW(Application.streamingAssetsPath + "/Hotfix.pdb");
#else
        www = new WWW("file:///" + Application.streamingAssetsPath + "/Hotfix.pdb");
#endif
        while (!www.isDone)
            yield return null;
        if (!string.IsNullOrEmpty(www.error))
            UnityEngine.Debug.LogError(www.error);
        byte[] pdb = www.bytes;
        fs = new MemoryStream(dll);
        p = new MemoryStream(pdb);
        try
        {
            appdomain.LoadAssembly(fs, p, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
        }
        catch
        {
            Debug.LogError("加载热更DLL失败,请确保已经通过VS打开Assets/Samples/ILRuntime/1.6/Demo/HotFix_Project/HotFix_Project.sln编译过热更DLL");
        }

        InitializeILRuntime();
        OnHotFixLoaded();
    }

    void InitializeILRuntime()
    {
#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)
        //由于Unity的Profiler接口只允许在主线程使用,为了避免出异常,需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler
        appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
        //这里做一些ILRuntime的注册,HelloWorld示例暂时没有需要注册的
    }

    void OnHotFixLoaded()
    {
        //HelloWorld,第一次方法调用
        //appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);

        // 实例化Hotfix中的类
        object obj = appdomain.Instantiate("Hotfix.HelloWorld");
        appdomain.Invoke("Hotfix.HelloWorld", "Test", obj);
    }

    private void OnDestroy()
    {
        if (fs != null)
            fs.Close();
        if (p != null)
            p.Close();
        fs = null;
        p = null;
    }

}

运行Unity,可以看到输出了Hello World
Hotfix Hello World 输出.png

至此,ILRuntime Hello World部分已经全部完成了。

总结:
ILRuntime使用时,除了Hotfix工程的配置稍微麻烦一些外,其他部分都不难,Demo做的很完善,基本是开箱即用。具体使用细节建议仔细阅读官方文档:
http://ourpalm.github.io/ILRu...

你可能感兴趣的:(unity,c#)