当前位置: 首页 > news >正文

InjectFix 热更新解决方案

简介

今天来谈一谈,项目种的客户端热更新解决方案。InjectFix是腾讯xlua团队出品的一种用于Unity中C#代码热更新热修复的解决方案。支持Unity全系列,全平台。与xlua的思路类似,InjectFix解决的痛点主要在于Unity中C#代码写的逻辑在发包之后无法更新,导致出现了严重的逻辑问题只能通过配置关闭功能或者利用资源更新来绕过bug这类问题。

相比较lua虚拟机热更的优点

相较于一般使用lua这种接入C#来进行热更新的如ulua之类的方案,InjectFix直接修改C#即可使用,老项目也可以使用,只要简单的接入相应的库,并依照补丁流程进行相应的操作即可。减少了额外学习一门语言的开销。

使用

官方链接:https://github.com/Tencent/InjectFix

1.注入(DLL插桩)

【InjectFix】-【Inject】来对我们的DLL进行自动插桩,需要在编辑器页面。
运行这个菜单工具后,这时IFix会根据我们提供的Config文件去给这些注册的类里面的每个方法插桩,它会直接修改 \Library\ScriptAssemblies\Assembly-CSharp.dll 这个文件,正常注入后即可得到一个拥有热更新能力的DLL文件。
所以我们需要在Editor目录下配置config文件添加需要热更的类。

原理如下。在.NET的CLR生成MSIL中间层语言时,在il代码中增加了一些跳转操作,如果检测到补丁就会执行相应的函数,本质上是修改了Unity生成的dll临时文件。
image.png
图1.1 注入后会修改MSIL代码
我们可以在il中清晰地看到这些逻辑。
image.png
如果IsPatche == false, 会跳转到IL_0021,否则顺序执行。

2.标注代码(制作补丁)

当有代码需要更新的时候,需要修改相应的代码。这里InjectFix主要提供了三种修改方式。这三种方式都是通过使用Attribute来标注被修改代码的途径来实现的。详细使用方式可以查看官方文档。这里说一下大概都是干什么的以及怎么用。

1.patch(用于修改一个函数)

比如

--- ImmortalGuideRootLogic.cs   (revision 323246)
+++ ImmortalGuideRootLogic.cs   (working copy)
@@ -81,6 +81,7 @@     
public GameObject m_Finished;      
public UIButton m_GoToGrowGuide;      
public UILabel m_TipsLabel;   //完成和等级不足公用一个label +    
[IFix.Patch]      
private void Start()     
{         if (m_DayItemList.Length != GlobeVar.IMMORTALGUIDE_TASKDAYCOUNT)@@ -87,7 +88,9 @@         { return;         } - +        //发消息请求仙人指路任务状态 +       CG_IMMORTALGUIDE_PROGRESS_REQ_PAK pak =new CG_IMMORTALGUIDE_PROGRESS_REQ_PAK();+       pak.SendPacket();         m_Instance = this;         m_LeftTime.text = "";         m_ProgressBonusPanel.SetActive(false);
//---------
}

这里代码的修改主要是在Start函数中增加了一些代码。增加了之后给函数标记Patch。这样之后生成Patch的时候,就能发现这个函数并生成相应的逻辑了。

2.Interpret(用于新增一个函数或者一个字段等)

[IFix.Patch]      void OnDestroy()      { 
+        OnDisable();
+  }
+ 
+    [IFix.Interpret] +    void OnDisable() +    {          m_tabBtnController.delTabWillChange -= TabChangeCheck;       m_Instance = null;          GameManager.PlayerDataPool.ChargeLTea.m_DelLTDrink -= UpDataChargeTeaRedDot;@@ -72,19 +79,21 @@          GameManager.PlayerDataPool.ChargeHTea.m_DelHTDrink -= UpDataChargeTeaRedDot;     }  
+    [IFix.Patch]     void UpdateRightBtnShow(TabIndex index, bool bShow)     { if((int)index >= 0 && (int)index < (int)TabIndex.Count) +  if((int)index >= 0 && (int)index < m_TabObject.Length)         {-           m_TabObject[(int)index].gameObject.SetActive(bShow);+            m_TabObject[(int)index].SetActive(bShow);        }     
} 

这里主要是想把原来用在Destroy的逻辑放到OnDisable中去。因为没有办法删除函数,所以直接删除函数中的逻辑,这里去掉了原来Destroy中的逻辑。然后新增了OnDisable函数用来相应相关的逻辑。
注意OnDisable在OnDestroy中进行了一次调用。这是因为在生成patch的时候会进行函数的裁剪。如果一个函数没有使用过的话,直接就被裁剪掉了。所以这里在别的函数用用一下,避免裁剪。

3.CustomBridge(用于告诉外界,虚拟机这里有一个类可以用)

因为本质上,InjectFix的Patch实现的所有的逻辑都是运行在一个用C#编写的虚拟机中
的,其实外界并不知道虚拟机中加载了什么样的逻辑。为了通知外界这里有一个逻辑可以让外界调用,需要用这个特性标注一下。
主要是为了以下这些使用情景。

  1. 修复代码赋值一个闭包到一个delegate变量;
  2. 修复代码的Unity协程用了yield return;
  3. 新增一个函数,赋值到一个delegate变量;
  4. 新增一个类,赋值到一个原生interface变量;
  5. 新增函数,用了yield return;

3.生成Patch

【InjectFix】-【Fix】生成补丁
按照上述方法标注了代码之后,就可以生成Patch了。即提取标注的代码,放到一个文件里。
在Unity的Menu中点击InjectFix->FixAll按钮执行对应的生成逻辑。
之后会在Client\IFixPatch路径下生成针对PC,ios和Android的三个patch包。再根据需要热更到的底包和资源版本号修改名字,提交上到CDN。

总结

IFix的原理主要包括两个部分:

  1. 自动插桩,首先在代码里面插桩,进入这些的函数的时候判断是否需要热更新,如果需要则直接跳转去执行热更新补丁中的IL指令。
  2. 生成补丁,将需要热更新的代码生成为IL指令。

周更

项目的热更新步骤是自动的。整体的过程是取到IFixPatch路径下的patch包。根据其名字取出这个patch对应的底包及资源路径。在执行热更新脚本的过程中,将这个patch进行改名,然后放到对应的资源更新的路径中,作为周更资源的一种进行更新。
底包在放出去之后,打开进行周更的时候,会下载patch包到机器的可读写路径中。加载完资源之后会进行尝试加载patch包的操作。加载了patch包之后,如果有被替换的逻辑,就会自动执行新的逻辑了。
这里可以很显然的看出,如果是资源更新完之前的逻辑出问题,热更新是无法解决的,所以这里需要额外注意

InjectFix 的缺点

确定注入的函数

要注入哪些函数其实也是根据配置生成的,详细可以看看官方文档。目前项目中的做法是注入了所有的Assembly-CSharp中的函数。所以在这之外的,比方说firstpass中的函数,就没有办法进行热更了。这一步会影响效率。

不能直接修改变量的值

整体的热更方案没有办法修改字段的值。所以如果修改一个变量的值,需要修改所有用到这个变量的逻辑。或者把变量改成以属性或者函数的方式来获得。

继承受限制

新增的类不可以继承外界的类。因为新的虚拟机没有实现这么多东西。

性能一般

补丁的逻辑性能很差,较外界的正常il2cpp(HybridCLR)差了大概3个数量级。所以频繁操作和复杂逻辑尽量不要热更,想办法绕过去。最好能不热更就不热更。

参考资料:https://www.jianshu.com/p/adf1cb2dbd3c


相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • LVS+Nginx高可用集群---keepalived原理与实战
  • 捷配PCB打样采用机械盲埋孔制造,有何优势?
  • 硅纪元视角 | 微软开发全新AI模型,革新电子表格处理效率!
  • 排队问题--逆序对应用
  • 使用druid对sql进行血缘解析
  • 去除重复字母
  • Python酷库之旅-第三方库Pandas(027)
  • 分类题解清单
  • 网络请求之urllib.request的使用(Get方式)
  • 数组 704.二分查找法
  • which 命令在Linux中是一个快速查找可执行文件位置的工具
  • el-table的selection多选表格改为单选
  • 【Diffusion学习】【生成式AI】Stable Diffusion、DALL-E、Imagen 背後共同的套路
  • 美式键盘 QWERTY 布局的来历
  • TS 入门(七):TypeScript模块与命名空间
  • [笔记] php常见简单功能及函数
  • 【comparator, comparable】小总结
  • 【技术性】Search知识
  • ➹使用webpack配置多页面应用(MPA)
  • Angular6错误 Service: No provider for Renderer2
  • golang 发送GET和POST示例
  • happypack两次报错的问题
  • Java小白进阶笔记(3)-初级面向对象
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • node学习系列之简单文件上传
  • Octave 入门
  • vue.js框架原理浅析
  • 闭包,sync使用细节
  • 从重复到重用
  • 翻译:Hystrix - How To Use
  • 复杂数据处理
  • 聊聊directory traversal attack
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 深度学习中的信息论知识详解
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 找一份好的前端工作,起点很重要
  • FaaS 的简单实践
  • Java性能优化之JVM GC(垃圾回收机制)
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • ​比特币大跌的 2 个原因
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • #NOIP 2014# day.1 T3 飞扬的小鸟 bird
  • #前后端分离# 头条发布系统
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • $.type 怎么精确判断对象类型的 --(源码学习2)
  • (4.10~4.16)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第14章泛型第2节(泛型类的类构造函数)
  • (Java入门)抽象类,接口,内部类
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布